본문 바로가기
개발

[Test] Test Code에 필요한 Test Fixture 재사용하기

by baau 2023. 7. 11.

SWM 프로젝트를 하면서 테스트 코드를 열심히 짜면서 개발하고 있다. TDD는 아니지만, API 하나하나를 개발할 때마다 단위 테스트를 작성하고 있다. 개발 시간이 배로 드는 것 같지만, 테스트 코드 덕분에 리펙토링이나 코드 리뷰 이후 코드를 수정하는 일에 있어서 자신감과 안정감을 가지고 코드를 수정할 수 있고, 매번 서버를 실행시키고 직접 테스트를 하는 것보다는 훨씬 쉽고 빠르게 테스트할 수 있다는 점에 매우 만족하면서 열심히 작성 중이다. 진짜 시간이 더 걸린다 외 다른 점들은 다 좋은 점들밖에 없는 것 같다!

 

단위 테스트에 필요한 데이터들을 생성하는 과정에서 시행착오를 겪으면서 조금 더 나은 방법을 알게 되어서 공유하려고 한다. (더 나은 방법이 있을 수 있는데.. 더 좋은 방법이 있다면 알려주십시오.. 환영합니다!)

작년에 테스트 코드를 작성한 것을 먼저 가져와봤다. 매 테스트 코드마다 테스트에 필요한 데이터들을 생성자나 빌더를 사용하여 생성해 주었다. 테스트 코드마다 테스트 데이터를 생성하기 때문에, 각 테스트들이 서로 영향을 주지 않지만, 너무 많은 중복 코드가 발생했다.

 

그래서 아래와 같이 테스트 데이터를 만드는 코드를 private method로 함수화를 하였다. 

이를 통해 코드의 중복은 많이 줄일 순 있었지만, 한 가지 문제가 있었다.

Member 관련 테스트 코드 외에도 다른 테스트에서 Member가 필요한 경우가 많았다. 따라서 Member에 대한 테스트 데이터가 필요한 테스트 코드마다 매번 private method를 만들어야 된다는 수고가 있었다.

 

 

그래서 SWM 프로젝트를 하면서는 private method를 사용하지 않고, Test Data Builder 패턴을 사용하였다.

  • 테스트 데이터를 생성하고 조작하는 데 사용되며, 테스트 코드 작성을 더 간결하고 유지보수하기 쉽게 만들어준다.
  • 빌더 클래스를 사용하여 테스트 데이터를 생성하는 방식이며, 빌더 클래스는 테스트 데이터를 초기화하고 필요한 속성을 설정하는 메서드를 제공한다.
public class MybookTestData {

    public static MyBook createMyBook() {

        return MyBook.builder()
                .shareable(false)
                .startDateOfPossession(LocalDateTime.now())
                .exchangeable(false)
                .showable(true)
                .readStatus(ReadStatus.TO_READ)
                .book(BookTestData.createBook())
                .deleted(false)
                .userId("test_userId")
                .build();
    }
    
    public static MyBook createDeletedMyBook() {

        return MyBook.builder()
                .shareable(false)
                .startDateOfPossession(LocalDateTime.now())
                .exchangeable(false)
                .showable(true)
                .readStatus(ReadStatus.TO_READ)
                .book(BookTestData.createBook())
                .deleted(true)
                .userId("test_userId")
                .build();
    }

    public static MyBook createDeletedMyBook(Book book) {

        return MyBook.builder()
                .shareable(false)
                .startDateOfPossession(LocalDateTime.now())
                .exchangeable(false)
                .showable(true)
                .readStatus(ReadStatus.TO_READ)
                .book(book)
                .deleted(true)
                .userId("test_userId")
                .build();
    }
    
	...
}

Test Data Builder 패턴을 사용하면서, 중복 코드를 제거하고 테스트 데이터를 생성하는 로직을 한곳에 집중되기 때문에 유지보수의 용이성을 느낄 수 있었지만, 아래와 같은 불편함이 있었다.

  • 함수명을 짓는 데에 어려움이 있었고, 함수명을 통해 해당 테스트 데이터의 모든 특성을 나타내기에는 어려우며 한 번에 파악하기 어려웠다.
    • 예를 들어, 로그인한 유저가 등록한 도서이며, 비공개된 책이며, 읽은 상태는 읽는 중이며, 삭제되지 않은 도서인 데이터를 표현하는 데에는 조금 어려움이 있었고, 표현한다고 해도 한 번에 파악하기 어려웠다.
  • 필드가 많으면 많을수록 생성하는 메서드는 곱으로 생성해야 하는데, 빌더 패턴을 이용하다 보니 코드량이 많아졌다.
  • 매개변수가 필요한 경우, 필요한 매개변수에 따라 코드가 배로 늘어난다.
    • 이 문제는 Builder를 반환함으로 써 특정 필드만 수정하여 재사용 가능하다.
public class MybookTestData {

    public static MyBook.MyBookBuilder createMyBook() {

        return MyBook.builder()
                .shareable(false)
                .startDateOfPossession(LocalDateTime.now())
                .exchangeable(false)
                .showable(true)
                .readStatus(ReadStatus.TO_READ)
                .book(BookTestData.createBook())
                .deleted(false)
                .userId("test_userId");
    }
}

MyBook myBook = MybookTestData.createMyBook().showable(false).build();

 

 

위의 같은 문제로 테스트 코드를 작성하면서 조금 찜찜한 느낌이 있어, 레퍼런스를 찾아보다가 https://jaehoney.tistory.com/274 의 블로그를 통해 enum을 사용하여 테스트 데이터를 만드는 방법에 대해서 알게 되었다.

 

 

위의 블로그를 참고하여 아래와 같이 Test Data Builder 패턴에서 enum을 사용해서 Fixture를 만드는 방식으로 수정하였다.

public enum MyBookFixture {

    COMMON_LOGIN_USER_MYBOOK(1L, "LOGIN_USER_ID", BookFixture.COMMON_BOOK.getBook(), ReadStatus.TO_READ,
            LocalDateTime.now(), true, false, false, false),
    DELETED_LOGIN_USER_MYBOOK(2L, "LOGIN_USER_ID", BookFixture.COMMON_BOOK.getBook(), ReadStatus.TO_READ,
            LocalDateTime.now(), true, false, false, true),
    NOT_SHOWABLE_LOGIN_USER_MYBOOK(3L, "LOGIN_USER_ID", BookFixture.COMMON_BOOK.getBook(), ReadStatus.TO_READ,
            LocalDateTime.now(), false, false, false, false),
    NOT_SHOWABLE_OTHER_USER_MYBOOK(4L, "OTHER_USER_ID", BookFixture.COMMON_BOOK.getBook(), ReadStatus.TO_READ,
            LocalDateTime.now(), false, false, false, false),
    COMMON_OTHER_USER_MYBOOK(5L, "OTHER_USER_ID", BookFixture.COMMON_BOOK.getBook(), ReadStatus.TO_READ,
            LocalDateTime.now(), true, false, false, false);

    private final Long id;
    private final String userId;
    private final Book book;
    private final ReadStatus readStatus;
    private final LocalDateTime startDateOfPossession;
    private final boolean showable;
    private final boolean exchangeable;
    private final boolean shareable;
    private final boolean deleted;

    public MyBook getMyBook() {
        return MyBook.builder()
                .id(id)
                .userId(userId)
                .book(book)
                .readStatus(readStatus)
                .startDateOfPossession(startDateOfPossession)
                .showable(showable)
                .exchangeable(exchangeable)
                .shareable(shareable)
                .deleted(deleted)
                .build();
    }

    public MyBook getMyBookWithBook(Book book) {
        return MyBook.builder()
                .id(id)
                .userId(userId)
                .book(book)
                .readStatus(readStatus)
                .startDateOfPossession(startDateOfPossession)
                .showable(showable)
                .exchangeable(exchangeable)
                .shareable(shareable)
                .deleted(deleted)
                .build();
    }

enum을 활용함으로써 제가 느꼈던 장점은 몇 가지 있었다.

  • 열거형 상수로 표현하다 보니, 함수명을 짓는 것보다 좀 더 자유로웠고 테스트 데이터의 특징을 살려 네이밍 하기가 편했다. 또한 한 번에 파악하기 쉬워 가독성도 좋아졌다고 느꼈다.
  • 다른 특징을 가진 테스트 데이터를 추가하기 위해서, 빌더 패턴을 사용하는 메서드가 아닌 열거형 상수를 추가하면 되므로 코드량이 많이 줄었다고 느꼈다.
  • 매개 변수가 필요한 경우, 매개변수를 사용하는 메서드를 하나 만들면 모든 열거형 상수로 선언된 테스트 데이터가 공통으로 사용할 수 있어 중복된 코드도 줄일 수 있었다.

 

위 블로그를 작성한 분의 의도와 조금 다를 수 있지만, 해당 레퍼런스를 참고한 이후

enum을 사용해서 Fixture를 만드는 방식으로 테스트 데이터를 생성하면서, 테스트 데이터의 특징을 파악하기 좋고, 테스트 데이터를 추가하는 데에도 많은 코드 양이 추가되지 않아, 만족하면서 테스트 코드를 작성하고 있다.

 

분명, 더 테스트 코드를 작성하면서 위의 방법의 문제점이나 찜찜한 기분을 느낄 것 같다. 당연히 더 나은 방법도 존재할 것이라 생각한다. 좀 더 프로젝트를 진행해 보면서, 더 경험해 보면서, 우리 팀이 더 사용하기 좋고, 더 나은 방법을 위해 고민하면서 테스트 코드를 작성해야겠다!!