테스트 픽스처(Test Fixture)를 어떻게 만드는 것이 좋은 걸까?

lango·2023년 12월 6일
12
post-thumbnail

🎬 들어가며

요즘 들어 테스트 코드를 작성하는 데 관심이 많아지면서 자연스럽게 테스트를 위한 다양한 기술이나 방법론, 개념들을 조금씩 학습하고 있다.

그런데 테스트를 위한 Fixture를 만드는 과정에서 문득 궁금한 점이 생겼다. 테스트를 위한 Fixture를 그냥저냥 만드는 법은 알고 있었지만, 어떻게 만드는 것이 좋은지는 잘 모르고 있었기에 고민할 수 밖에 없었다.

조금 더 구체적으로 이해를 돕기 위해 간단한 테스트 케이스를 예로 들어보겠다. 만약 댓글을 등록하기 위한 테스트를 진행한다고 가정해 보자. 댓글을 등록하기 위해서는 다음과 같은 정보가 필요하다.

  • 게시글이나 댓글을 작성할 수 있는 회원(작성자) 정보
  • 댓글을 등록할 게시글 정보

위 두 가지 Fixture는 댓글 등록 테스트 케이스의 검증을 위해서 준비되어야 한다. 회원과 게시글 정보가 준비되면 댓글을 등록하는 테스트 케이스를 수행할 수 있게 된다.

결국, 회원이나 게시글 같은 Fixture에 대해서 아래와 같은 고민이 생겼다.

테스트의 독립성을 지키기 위해 테스트 케이스마다 Fixture를 만드는 것이 좋을까?
테스트 케이스마다 중복으로 발생하는 Fixture를 setUp 등으로 통합하는 것이 좋을까?

위 두 가지 고민이 이 글을 작성하기 위한 동기부여로 이어졌다. 그래서 이번 글에서는 위에서 언급한 두 가지 방법을 이용해 Fixture를 만들어보고, 필자는 어떤 방법을 사용하려 하는지를 전달하려 한다.




🧐 그런데 Test Fixture가 뭐야?

예시 코드를 작성하기 전에 앞서, 댓글 테스트와 관련된 회원 정보와 게시글 정보가 Fixture라고 암묵적으로 언급했었다. 이 정보들이 왜 Test Fixture라고 불리는 걸까? 이 상황을 인지하고 Test Fixture라는 단어의 의미를 생각해 보자.

Test Fixture? 무슨 뜻일까? 위키피디아를 보면, 이에 대해 아래와 같이 설명하고 있다.

A test fixture is a device used to consistently test some item, device, or piece of software. Test fixtures are used in the testing of electronics, software and physical devices.

(번역)

Test Fixture는 특정 항목, 장치 또는 소프트웨어를 일관되게 테스트하는 데 사용되는 장치이다. Test Fixture는 전자 제품, 소프트웨어 및 물리적 장치를 테스트하는 데 사용된다.

이번에는 chatGPT에게 Test Fixture에 대해서 물어보니 다음과 같은 답변을 받았다.

Test Fixture는 테스트를 수행하기 전에 필요한 상태나 환경을 설정하는 것을 의미합니다. 예를 들어, 데이터베이스 연결을 성립하거나 테스트 데이터를 생성하는 등의 작업이 Test Fixture에 해당합니다.

결국, 댓글 작성이라는 테스트 수행을 위한 데이터인 회원과 게시글 정보가 Test Fixture에 해당된다는 것을 알 수 있었고, 이러한 Fixture의 의미는 테스트의 상태와 환경을 일관적으로 고정하는 어떠한 개념이라고 이해하였다. 사실 잘 이해한 것이 맞는지 모르겠다.

여기까지 Test Fixture의 의미를 간단히 살펴보았다. 그런데, 회원이나 게시글과 같은 Fixture를 여태 만들어온 것처럼 만들면 되는데, 굳이 왜 일관성 있게 만들어야 하는 걸까?




✨ Test Fixture를 사용하는 본질적인 목적

왜 테스트의 상태와 환경을 일관되도록 만들어야 할까? code-craftsmanship-saturdays라는 미국의 기술그룹이 언급하는 Fixture 관련 글을 살펴보면, Test Fixture의 목적에 대해서 다음과 같이 말하고 있다.

The purpose of a test fixture is to ensure that there is a well known and fixed environment in which tests are run so that results are repeatable.

(번역)

Test Fixture의 목적은 테스트가 실행되는 환경의 일관성을 높이고, 그 결과를 반복 가능하도록 보장하는 것이다.

이와 더불어, JUnit은 Fixture와 관련하여 다음과 같이 언급하고 있다.

Tests need to run against the background of a known set of objects. This set of objects is called a test fixture. When you are writing tests you will often find that you spend more time writing the code to set up the fixture than you do in actually testing values.

To some extent, you can make writing the fixture code easier by paying careful attention to the constructors you write. However, a much bigger savings comes from sharing fixture code. Often, you will be able to use the same fixture for several different tests. Each case will send slightly different messages or parameters to the fixture and will check for different results.

(번역)

동일하거나 유사한 객체들이 사용되는 여러 테스트가 있을 경우 실제로 값을 검증하는 과정보다 Fixture를 설정하는 데 더 많은 시간을 할애하는 경우도 생길 수 있기 때문에, 동일한 Fixture를 여러 테스트가 공유할 수 있도록 설정한다면 상황에 따라 성능 향상과 비용 절감을 얻을 수 있다.

위 자료들의 글을 살펴보면서 여태까지 아무 생각 없이 작성했던 Fixture 코드들이 떠올랐다.

사실 댓글 작성이라는 테스트를 위한 회원과 게시글 데이터를 만드는 것은 크게 어렵지 않을 수도 있다. 그냥 메소드 레벨이든 클래스 레벨이든 Fixture를 설정하여 댓글 작성 테스트의 정상 동작이라는 결과를 도출하면 문제는 없다.

다만, 단순히 여기서 끝내지 않고 회원 및 게시글 정보가 댓글 작성 테스트를 비롯해 다른 테스트들에 영향을 미치지는 않는지, 또 여러 테스트에서 공유해야 하는 경우는 없는지, 또 Fixture들의 중복을 줄일 수 있는지와 같은 더 나은 테스트를 꾸리기 위한 기술적 물음표를 던져야 한다.

Test Fixture의 의미를 알고 난 후!

테스트를 진행할 때마다 (Fixture를 포함하여) 주어진 상태나 환경이 변하게 된다면, 테스트가 성공했다한들, 이는 테스트의 신뢰성을 떨어뜨리고 테스트의 목적 자체를 퇴색시키기 때문에 좋은 테스트 케이스가 아니라고 생각한다.

그러므로, 특정 상황에 적절한 Test Fixture를 만들어 모든 테스트 케이스의 상태와 환경에 대한 독립성과 고정 상태(일관성)을 유지한다면, 코드의 중복도 줄이고 테스트를 언제 어디서 진행하든 동일한 결과를 만족할 수 있는 구조로 발전할 수 있다고 느껴졌다. 그리고 이러한 테스트 코드의 발전은 비용 절감으로 이어질 수 있을 것이다.

그리고 비단, Test Fixture를 만드는 것뿐만 아니라 테스트를 작성하는 의도부터 시작하여 좋은 테스트 코드를 작성하기 위한 다양한 기술적 지식과 방법론들에 대한 필요성을 느끼게 되었다.




👨🏻‍💻 직접 Fixture 만들어보기

재미없게 이론적인 이야기만 잔뜩 늘어놓았다. 이제부터 처음부터 고민했던 Fixture 생성 방법을 모두 직접 적용해보고 이에 대한 나의 의견을 전달하려 한다.

먼저 예제 코드에 쓰일 개발환경과 관련된 사항은 다음과 같다.

  • Spring Boot 3.0.2
  • Java 17
  • JUnit5 + AssertJ

다음으로 Fixture를 만들기 위한 댓글 관련 테스트 시나리오는 다음과 같다.

#1. 댓글 엔티티에 대한 단위 테스트를 진행한다.
#2. 댓글 엔티티를 생성하는 테스트 케이스 2가지와 댓글 엔티티를 생성한 후 수정하는 테스트 케이스 1가지로 테스트 코드를 작성한다.

그리고 위 세 가지의 테스트 케이스에 필요한 Fixture는 다음과 같다.

  • 회원 정보(게시글 작성자 및 댓글 작성자) 2개
    이해를 돕기위해 게시글 작성자와 댓글 작성자를 구분했다.
  • 게시글 정보 1개

*예제 코드의 퀄리티가 처참하니, 너그러이 양해를 바란다.
*코드의 가독성을 위해 예제 코드에서 import 구문은 생략했음을 알린다.

테스트의 독립성을 지키기 위해 테스트 케이스마다 Fixture 만들기

먼저 댓글 등록 및 수정 테스트에 대한 회원 및 게시글 Fixture를 테스트 메서드 레벨에서 만들어보았다.

class CommentTest {

    @DisplayName("댓글 작성시 투표하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenPickIsEmpty() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = Member.builder()
                .email("owner@test.com")
                .nickname("owner")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Member commentWriter = Member.builder()
                .email("guest@test.com")
                .nickname("guest")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Board board = Board.builder()
                .title("집에서 잠옷으로 입을 옷을 골라주세요.")
                .content("content")
                .member(boardWriter)
                .categoryBoard(CategoryBoard.VOTE)
                .deadLine(LocalDateTime.now())
                .attachPaths(attachPaths)
                .build();

        // when
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .build();

        // then
        assertThat(comment.getPick()).isZero();
    }

    @DisplayName("댓글 작성시 첨부파일을 업로드하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenAttachmentIsEmpty() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = Member.builder()
                .email("owner@test.com")
                .nickname("owner")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Member commentWriter = Member.builder()
                .email("guest@test.com")
                .nickname("guest")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Board board = Board.builder()
                .title("집에서 잠옷으로 입을 옷을 골라주세요.")
                .content("content")
                .member(boardWriter)
                .categoryBoard(CategoryBoard.VOTE)
                .deadLine(LocalDateTime.now())
                .attachPaths(attachPaths)
                .build();

        // when
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(1)
                .build();

        // then
        assertThat(comment.getContent()).isEqualTo("저는 수면잠옷을 더 추천드려요..!");
    }

    @DisplayName("댓글 수정시 투표를 변경할 수 있다.")
    @Test
    void changeCommentPick() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = Member.builder()
                .email("owner@test.com")
                .nickname("owner")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Member commentWriter = Member.builder()
                .email("guest@test.com")
                .nickname("guest")
                .password("test")
                .type(Type.LOCAL)
                .build();
        Board board = Board.builder()
                .title("집에서 잠옷으로 입을 옷을 골라주세요.")
                .content("content")
                .member(boardWriter)
                .categoryBoard(CategoryBoard.VOTE)
                .deadLine(LocalDateTime.now())
                .attachPaths(attachPaths)
                .build();
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(1)
                .build();

        // when
        comment.changeComment("저는 수면잠옷을 더 추천드려요..!", 3, "");

        // then
        assertThat(comment.getPick()).isEqualTo(3);
    }

}

위의 CommentTest 코드를 보면, 모든 테스트 메서드 내부에서 각각 2개의 회원 정보와 1개의 게시글 정보를 생성했다.

뭔가 무식하고 단순하게 Fixture를 만들긴 했으나 어쨋든 테스트 메서드마다 만들어진 Fixture가 독립적으로 간주되어 테스트 케이스간 영향을 주지 않게 된다.

테스트 케이스마다 만들어야 하는 Fixture를 setUp으로 통합하기

이번에는 앞에서 테스트 메서드마다 일일이 만들었던 회원 정보 2개와 게시글 1개에 대한 Fixture를 만드는 과정을 setUp으로 통합하여 중복 구문을 줄여보자.

class CommentSetUpTest {

    private Member commentWriter;
    private Board board;

    @BeforeEach
    void setUp() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = Member.builder()
                .email("owner@test.com")
                .nickname("owner")
                .password("test")
                .type(Type.LOCAL)
                .build();
        commentWriter = Member.builder()
                .email("guest@test.com")
                .nickname("guest")
                .password("test")
                .type(Type.LOCAL)
                .build();
        board = Board.builder()
                .title("집에서 잠옷으로 입을 옷을 골라주세요.")
                .content("content")
                .member(boardWriter)
                .categoryBoard(CategoryBoard.VOTE)
                .deadLine(LocalDateTime.now())
                .attachPaths(attachPaths)
                .build();
    }

    @DisplayName("댓글 작성시 투표하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenPickIsEmpty() {
        // when
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .build();

        // then
        assertThat(comment.getPick()).isZero();
    }

    @DisplayName("댓글 작성시 첨부파일을 업로드하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenAttachmentIsEmpty() {
        // when
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(1)
                .build();

        // then
        assertThat(comment.getContent()).isEqualTo("저는 수면잠옷을 더 추천드려요..!");
    }

    @DisplayName("댓글 수정시 투표를 변경할 수 있다.")
    @Test
    void changeCommentPick() {
        // changeCommentPick 메서드만의 given Fixture 생성
        Comment comment = Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(1)
                .build();

        // when
        comment.changeComment("저는 수면잠옷을 더 추천드려요..!", 3, "");

        // then
        assertThat(comment.getPick()).isEqualTo(3);
    }

}

이번 CommentSetUpTest 코드는 setUp 메서드에서 회원과 게시글에 대한 Fixture를 만들어 createCommentWhenPickIsEmpty 메서드와 createCommentWhenAttachmentIsEmpty 메서드, 그리고 changeCommentPick 메서드에서 만들어진 commentWriter와 board Fixture를 사용하도록 바꿔보았다.

중복으로 발생하던 FIxture 생성 구문을 setUp 메서드로 통합하게 되면서 각각의 테스트 케이스에 대한 가독성이 좋아졌다고 볼 수 있다.




💡 저는 ___ 방법으로 Fixture를 만드려고 해요.

이전까지 테스트 메서드에서 일일이 Fixture를 만들어도 보고, 중복되어 만들어지는 Fixture 생성 구문을 setUp으로도 통합해도 보았다. 그렇다면, 어떤 방법이 더 좋은 것이고 무엇을 사용해야 할까?


결론부터 말하자면, 필자는

Test Fixture를 만들 때, setUp에서 한 번에 만드는 방법보다는 테스트케이스마다 일일이 만드는 방법을 선호하고 지향한다고 말하고 싶다.


사실 이 방법들 중에서 정답은 당연히 없고, 특정한 상황에 따라, 팀 개발 정책에 따라 융통성 있게 두 가지 방법을 모두 사용할 줄 알아야 한다고 생각한다. 그럼에도 불구하고 1번 방법을 택한 이유를 커리어와 기술이라는 2가지 키워드로 설명할 수 있을 것 같다.

먼저 커리어적으로 현재 필자는 시니어 개발자도 아니고 일개 주니어 경력의 개발자이다. 그렇기에 기술적인 능력이 아직은 매우 부족하다고 생각한다. 그래서 내가 작성한 코드를 동료나 선배들에게 리뷰를 받을 때 가독성이 좋아야 한다.

헌데 여기서 setUp 메서드를 통해 Fixture를 한 번에 만드는 것이 코드의 가독성을 더욱 높이는 것 아닌가요? 라고 반박해 주신다면 그 말도 일리 있고 합당한 주장이라고 생각한다. 다만, 복잡하고 다양한 테스트 클래스가 존재할 경우 테스트 케이스마다 어떤 Fixture가 필요한지 확인하려면 일일이 setUp에서 생성되는 Fixture를 확인해야 하는 번거로움이 생길 것으로 예상되는데, 이를 확인하는 비용이 크다고 생각이 든다. 그리고 기술적 이유로는 Fixture로 인한 테스트 케이스 간의 결합이 생기기 때문에 테스트의 독립성을 해친다고 생각한다.

다시 한번 정리하자면, 현재 필자는 Fixture의 공유 상태를 완벽히 파악할 정도로 대단한 수준이 아니다. 그래서 조금 중복이 발생하더라도 일일이 Fixture를 만드는데, 다른 사람이 읽고 유지보수할 수 있는 수준으로 코드 퀄리티를 올리려 한다. 물론 setUp을 완전히 배제한다는 것은 절대 아니다. 단지 우선순위의 차이가 생겼다는 표현이 적절한 것 같다.

Fixture 생성 구문 개선하기

setUp에서 한 번만 생성하면 끝나는 Fixture들을 테스트 메서드마다 생성한다면 중복을 피할 수는 없다. 그렇다면 중복을 허용하더라도, 최대한 이 부분을 보기 좋게 바꿔볼 수 있지 않을까? 필자가 생각해본 대안은 두 가지 정도가 있다.

  • private 메서드를 활용해 Fixture 만들기
  • 별도 Fixture 클래스를 통해 Fixture 만들기

그리고 이 대안들을 통해서 다음과 같은 이점을 얻을 수 있게 된다고 생각한다.

  1. 테스트 코드가 이전보다 읽기 쉬워진다.
  2. 코드 라인 수가 줄어든다.
  3. Fixture의 상태나 영향을 신경쓰지 않아도 된다.

백문이 불여일견이라고 앞서 만들었던 코드에 이 대안들을 적용해보자.

테스트 클래스 내부에서 만드는 Fixture를 private 메서드로 추출하기

// 테스트에 필요한 Fixture를 생성하기 위해 어래 3가지의 private 팩토라 메서드를 활용한다.

private Comment createCommentWithoutPick(Member commentWriter, Board board) {
    return Comment.builder()
            .member(commentWriter)
            .board(board)
            .content("저는 수면잠옷을 더 추천드려요..!")
            .attachmentUrl("attach-url")
            .build();
}

private Board createBoard(Member boardWriter, List<String> attachPaths) {
    return Board.builder()
            .title("집에서 잠옷으로 입을 옷을 골라주세요.")
            .content("이전에 입던 옷이 오래되어서 새 옷을 사야해요.. 부탁합니다 여러분.")
            .member(boardWriter)
            .categoryBoard(CategoryBoard.VOTE)
            .deadLine(LocalDateTime.now())
            .attachPaths(attachPaths)
            .build();
}

private Member createMember(String mail, String owner) {
    return Member.builder()
            .email(mail)
            .nickname(owner)
            .password("test")
            .type(Type.LOCAL)
            .build();
}

먼저 댓글 등록에 대한 하나의 테스트 케이스에 필요한 Fixture들을 private 메서드로 분리하였다.

@DisplayName("댓글 작성시 투표하지 않아도 정상적으로 등록된다.")
@Test
void createCommentWhenPickIsEmpty() {
    // given
    List<String> attachPaths = List.of("image1.png", "image2.png");
    Member boardWriter = createMember("owner@test.com", "owner");
    Member commentWriter = createMember("guest@test.com", "guest");
    Board board = createBoard(boardWriter, attachPaths);

    // when
    Comment comment = createCommentWithoutPick(commentWriter, board);

    // then
    assertThat(comment.getPick()).isZero();
}

그리고 테스트케이스에서는 분리한 private 메서드를 사용하도록 변경했다.

Fixture 생성을 위한 createMembercreateBoardcreateComment 등의 private 메서드를 활용함으로 중복을 줄일 수 있을 뿐만 아니라 테스트 케이스간의 결합도를 낮추고 독립성이 높아졌다고 느껴진다. 이전보다 코드를 읽기 더 편해지지 않았는가?

별도 Fixture 클래스의 static 메서드를 통해 Fixture 만들기

자주 사용되는 Fixture들을 private 메서드로 추출하여 사용하는 것도 좋지만, 회원과 게시글과 같은 Fixture를 단위 테스트뿐만 아니라 통합 테스트에서도 사용할 경우라고 한다면 어떻게 해야 할까?

여러 곳에서 사용되는 Fixture를 외부의 클래스에서 만들도록 분리할 수도 있다. 바로 외부에서 회원과 게시글 정보를 제공할 Fixture 클래스를 만들어보자.

// 회원 정보를 제공할 Fixture Class
public class MemberFixture {
    public static Member createMember(String email, String nickname) {
        return Member.builder()
                .email(email)
                .nickname(nickname)
                .password("test")
                .type(Type.LOCAL)
                .build();
    }
}

// 게시글 정보를 제공할 Fixture Class
public class BoardFixture {
    public static Board createBoard(Member member, List<String> attachPaths) {
        return Board.builder()
                .title("집에서 잠옷으로 입을 옷을 골라주세요.")
                .content("content")
                .member(member)
                .categoryBoard(CategoryBoard.VOTE)
                .deadLine(LocalDateTime.now())
                .attachPaths(attachPaths)
                .build();
    }
}

회원과 게시글에 대한 Fixture 모두를 하나의 클래스에서 만들어 제공할 수도 있지만, 보다 조금 더 명확한 책임을 가지도록 하기 위해 MemberFixture 클래스와 BoardFixture 클래스 2가지로 Fixture 클래스를 작성하였다.

💡 각각의 Fixture들을 static 메서드로 선언한 이유는 아무래도 인스턴스 생성없이 호출할 수 있기에 테스트 코드를 작성할 때 편리하다.

이제 댓글 테스트에서 이들을 가져다 쓰기만 하면 된다.

class CommentTest {
    @DisplayName("댓글 작성시 투표하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenPickIsEmpty() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = MemberFixture.createMember("owner@test.com", "owner");
        Member commentWriter = MemberFixture.createMember("guest@test.com", "guest");
        Board board = BoardFixture.createBoard(boardWriter, attachPaths);

        // when
        Comment comment = createCommentWithoutPick(commentWriter, board);

        // then
        assertThat(comment.getPick()).isZero();
    }

    @DisplayName("댓글 작성시 첨부파일을 업로드하지 않아도 정상적으로 등록된다.")
    @Test
    void createCommentWhenAttachmentIsEmpty() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = MemberFixture.createMember("owner@test.com", "owner");
        Member commentWriter = MemberFixture.createMember("guest@test.com", "guest");
        Board board = BoardFixture.createBoard(boardWriter, attachPaths);

        // when
        Comment comment = createCommentWithoutAttachment(commentWriter, board);

        // then
        assertThat(comment.getContent()).isEqualTo("저는 수면잠옷을 더 추천드려요..!");
    }

    @DisplayName("댓글 수정시 투표를 변경할 수 있다.")
    @Test
    void changeCommentPick() {
        // given
        List<String> attachPaths = List.of("image1.png", "image2.png");
        Member boardWriter = MemberFixture.createMember("owner@test.com", "owner");
        Member commentWriter = MemberFixture.createMember("guest@test.com", "guest");
        Board board = BoardFixture.createBoard(boardWriter, attachPaths);
        Comment comment = createComment(commentWriter, board, 1);

        // when
        comment.changeComment("저는 수면잠옷을 더 추천드려요..!", 3, "");

        // then
        assertThat(comment.getPick()).isEqualTo(3);
    }

    private Comment createComment(Member commentWriter, Board board, int pick) {
        return Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(pick)
                .attachmentUrl("attach-url")
                .build();
    }

    private Comment createCommentWithoutPick(Member commentWriter, Board board) {
        return Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .attachmentUrl("attach-url")
                .build();
    }

    private Comment createCommentWithoutAttachment(Member commentWriter, Board board) {
        return Comment.builder()
                .member(commentWriter)
                .board(board)
                .content("저는 수면잠옷을 더 추천드려요..!")
                .pick(1)
                .build();
    }
}

이렇게 테스트 케이스에서 발생하는 Fixture 생성 구문을 private 메서드로 추출하거나, 외부의 Fixture 클래스를 이용해 가독성이 나빴던 코드를 아주 조금(?)이라도 개선할 수 있었다.




💬 마치며

다른 선배 개발자분들과 공감대가 형성되다..!

여기서 다루었던 내용과 관련하여 선배 개발자분들의 글이나 자료들을 찾아보니, 캐치테이블의 박우빈님과 인프런의 향로님 모두 유사한(?) 의견을 가지고 계신 것으로 보여졌다. 왠지 모르게 나 혼자 공감대가 형성되는 이유는 뭘까..?

박우빈님의 경우 인프런에서 테스트 관련 강의를 진행하고 계시는데 이번 글의 주제와 유사한 내용을 해당 강의 질문 글에서 찾아볼 수 있었다. 우빈님의 답변을 살펴보면 setUp 과정은 각 테스트에서 전혀 몰라도 되는 준비 과정만을 구성하신다고 하신다.

향로님의 경우는 기억보다 기록을이라는 티스토리 블로그에 직접 작성하신 글을 통해서 관련 내용을 찾아볼 수 있었는데, 이미 21년도에 내가 생각했던 방법들과 유사하지만 더욱 질적인 내용을 전달해주고 계셨다. 해당 글은 내가 장황하게 늘어놓았던 내용들을 간단명료하고 단순하게 전달해주고 계시니 꼭 읽어보길 바란다.

마무리를 위한 맺음말

마지막으로 Test Fixture를 setUp에서 한 번만 구성하든 테스트 케이스마다 일일이 구성하든 정답은 없다는 것을 다시 한번 알린다. 그리고 나 따위가 이 글을 읽으시는 동료 및 선배분들에게 다양한 Fixture 구성 방식들 중에서 어떤 방식을 사용하시라고 감히 말할 수도 없다고 생각한다.

고작 주니어 개발자라서 아니, 주니어 개발자이기 때문에 적재적소에 좋은 코드를 짜기란 쉽지 않음을 알기에 현시점에서 나에게 도움이 되는 Test Fixture 구성방식의 우선순위를 둘 뿐이다. 라고 전달하고 싶었을 뿐이다.

그냥 이 사람은 Test Fixture를 이렇게 만드는구나 라는 정도로 생각해 주시면 감사할 것 같다.

결론: 테스트 코드 잘 짜는 개발자가 되고 싶다. 🫠




참고 자료

이번 글에서 직접 작성했던 예제 코드는 Github에서 확인하실 수 있습니다.

좋지 않은 코드 퀄리티에 대해서는 거듭 양해를 구합니다. 그리고 잘못된 내용에 대한 지적은 언제든 환영입니다! 맘껏 태클 걸어주십시오.

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글