Server/JUnit, Spock

JUnit - Enum으로 Test Fixture 사용하기! (feat. Object Mother, Test Data Builder)

JaeHoney 2022. 9. 16. 08:20

테스트 데이터 생성

테스트 코드를 작성할 때 테스트 데이터는 어떻게 생성해야 할까..? 예시를 들어보자!

 

유저 조회, 게시글 생성, 댓글 등록 등 기능을 테스트할 때 모두 Member 생성이 필요하다.

 

각 테스트에서 일일이 생성자로 Member를 호출해도 되지만, 이를 재사용할 수 있다면 유지보수성이 좋은 코드가 된다.

 

잘 알려진 방법으로는 아래 2가지가 있다.

 

1. Test Data Builder 패턴

 

Test Data Builder 패턴은 Builder를 활용해서 테스트 데이터를 생성하는 패턴이다.

public class UserStubGenerator {

    public static UserStub.Builder generateUserStub() {
        return UserStub.builder()
                .loginId("tid1234")
                .password("password1234@");
    }

}

위와 같은 Builder를 반환하는 클래스가 있다면 해당 빌더를 재사용해서 Entity를 생성할 수 있다.

 

Builder를 사용하는 이유는 아래와 같이 필요한 경우 특정 필드만을 수정해서 테스트가 가능하기 때문이다.

// 사용처 (loginId 특수문자가 있을 시 실패하는 케이스)
User testUser = UserStubGenerator.generateUserStub()
                        .loginId("@#($*$(@)")
                        .toEntity();

해당 방법도 좋은 방법이다.

 

하지만, 테스트 객체의 상태를 확인하기 어렵고 도메인 파악이 어렵다는 단점이 있다.

 

2. Object Mother 패턴

Object Mother 패턴은 Martin Fowler가 제시한 테스트 데이터를 생성하는 방법이다.

- (참고) https://martinfowler.com/bliki/ObjectMother.html

 

간략히 설명하면 Object Mother는 테스트할 Instance를 만들어주는 클래스를 말한다.

 

EasyRandom, Fixture Monkey 같은 도구를 사용할 수도 있지만, 이번 포스팅에서는 Enum을 사용해서 Fixture를 만들어서 사용하는 방식에 대해 작성한다.

  • 우아한 형제들 프로젝트에서 사용하는 것을 확인했고, 나쁘지 않은 것 같아서 블로그에서 정리한다!

Fixture

Fixture는 '고정되어 있는 물체'를 의미한다.

 

JUnit 테스트의 Fixture는 테스트 실행을 위한 베이스라인으로 사용되는 객체들의 고정된 상태이다.


Fixture의 목적은 결과를 반복가능할 수 있도록 알 수 있고, 고정된 환경에서 테스트할 수 있음을 보장한다.

 

예시를 들어보자.

  • 게시글을 생성할 떄는 유저 정보가 필요하다.
  • 댓글을 생성할 때도 유저 정보가 필요하다.
  • 결제를 할 때도 유저 정보가 필요하다.
  • 유저 정보 조회를 위해서도 유저 정보가 필요하다.

가령, 서비스 단위 테스트를 한다고 생각해보자.

 

대부분의 테스트에서는 유저를 생성하고, 해당 유저 정보를 사용해서 테스트할 엔터티를 생성하거나 해당 유저 정보를 조회하는 등의 작업을 수행해야 한다.

 

아래의 예시를 보자.

Fixture

Fixture는 Enum으로 정의한다.

public enum MemberFixture {

    ADMIN("violetbeach1", "관리자", "ti641924@gmail.com", UserRole.ADMIN, false),
    NORMAL("jaehoney2", "유저 닉네임", "oir232@gmail.com", UserRole.MEMBER, false),
    BLOCK("tistory13", "Test nickname", "325232@gmail.com", UserRole.MEMBER, true),

    ;

    private final String userId;
    private final String nickname;
    private final String email;
    private final UserRole userRole;
    private final boolean block;

    MemberFixture(String userId,
                  String nickname,
                  String email,
                  UserRole userRole,
                  boolean block) {
        this.userId = userId;
        this.nickname = nickname;
        this.email = email;
        this.userRole = userRole;
        this.block = block;
    }

    public Member getMember() {
        return new Member(userId, nickname, email, userRole, block);
    }

}

이후에는 다양한 바운디드 컨텍스트의 클래스에 대해 테스트할 때 해당 Enum을 사용해서 Member를 생성할 수 있다.

 

즉, 재사용성이 증가한다.

 

추가로 여기서 게시글 Fixture를 만든다면 아래와 같이 구현할 수 있다.

public enum PostFixture {

    NOTICE("공지사항", "공지사항입니다.", PostType.NOTICE),
    CHAT("게시글", "게시글입니다.", PostType.CHAT)
    ;

    private final String title;
    private final String content;
    private final PostType type;

    PostFixture(String title, String content, PostType type) {
        this.title = title;
        this.content = content;
        this.type = type;
    }

    public Post getPost(Member author) {
        return new Post(
            author, title, content, type, PostStatus.ACTIVE);
    }

    public Post getPost(Member author, String status) {
        return new Post(
                author, title, content, type, PostStatus.valueOf(status));
    }
}

이후에 해당 데이터의 생성을 할 때는 아래와 같이 사용하면 된다.

 

postRepository.save(NOTICE.getPost(member));

 

엔터티 뿐만 아니라 자주 사용하는 DTO나 Command에도 해당 방법을 적용해서 다양한 컨텍스트의 테스트를 정말 깔끔하게 작성할 수 있다.