FireDrago

[bobzip] 테스트하기 편한 엔티티 생성 (Builder) 본문

프로젝트

[bobzip] 테스트하기 편한 엔티티 생성 (Builder)

화이용 2024. 6. 10. 10:23

문제점

- 비지니스 핵심 엔티티가 여러 필드를 가지고 있고, 연관관계 또한 복잡하다면, 어떻게 테스트를 해야할까?

@Entity
@Getter
public class Recipe {

    @Id
    @GeneratedValue
    @Column(name = "recipe_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id")
    private Member member;

    @NotNull
    @Size(max = 50)
    private String title;

    @Column(length = 100)
    private String instruction;

    @Embedded
    private UploadFile thumbnail;

    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RecipeIngredient> recipeIngredients = new ArrayList<>();

    @OneToMany(mappedBy = "recipe", cascade = CascadeType.ALL, orphanRemoval = true)
    private List<RecipeStep> recipeSteps = new ArrayList<>();

레시피 엔티티의 필드들인데, 여러 엔티티와 관계를 맺고 있음을 볼 수 있다.

Member , RecipeStep, RecipeIngredient 객체와 관계를 맺고 있는데, 

레시피 엔티티를 사용하는 리포지토리 테스트를 작성할때, 연관관계에 있는 모든 객체들을 함께 생성해야할까?

 

예를들어 페이징 기능을 테스트해야 한다면 적어도 5개 이상의 테스트 데이터가 필요한데,

실제 Recipe 연관관계를 모두 설정한 엔티티를 5개 생성한다면, 테스트코드보다 엔티티 생성 코드가 더 많아 질 것이다.

 

해결

- Builder 패턴을 이용하여 객체생성에 유연성을 주자!

 

Lombok 은 @Builder 어노테이션을 통해, Builder 패턴을 사용하기위한,

내부 Builder 클래스와 연쇄 메서드를 생성해준다.

Builder 패턴을 사용하면, 객체생성의 유연성을 제공해준다.

실제 비지니스 로직과는 다른 형태지만, 테스트를 위한 간단한 형태의 객체생성을 가능하게 한다.

리포지토리 테스트이므로, 엔티티가 실제 비지니스 로직과 다른 형태라도 문제가 되지 않는다고 판단했다. 

//==생성 메서드==//
    @Builder
    public Recipe(List<RecipeIngredient> recipeIngredients,
                   List<RecipeStep> recipeSteps, String instruction,
                   Member member, String title, UploadFile uploadFile) {
        this.member = member;
        this.title = title;
        this.instruction = instruction;
        this.thumbnail = uploadFile;

        if (recipeSteps != null) {
            for (RecipeStep recipeStep : recipeSteps) {
                addRecipeStep(recipeStep);
            }
        }
        if (recipeIngredients != null) {
            for (RecipeIngredient recipeIngredient : recipeIngredients) {
                addRecipeIngredient(recipeIngredient);
            }
        }
    }

    //==연관관계 메서드==//
    public void addRecipeStep(RecipeStep recipeStep) {
        recipeSteps.add(recipeStep);
        recipeStep.addRecipe(this);
    }

    public void addRecipeIngredient(RecipeIngredient recipeIngredient) {
        recipeIngredients.add(recipeIngredient);
        recipeIngredient.addRecipe(this);
    }
}

 

위 Recipe 엔티티 생성 builder 와 연관관계 편의 메서드를 함께 설정했다. 

Builder를 사용하지 않아도, 점층적 생성자 방식을 사용할수 있지만, 점층적 생성자 방식은 코드가 너무 길어질 위험

파라미터에 null을 입력해야 하는 불편함이 발생할 수 있기때문에, Builder가 더 낫다고 판단했다.

 

@Builder를 사용하여, 테스트코드에서는 객체생성을 간단한 형태로 만들 수 있게 되었다.

레시피 제목과 설명만으로 레시피 객체를 생성할 수 있게 되었다.

@Test
void recipeSaveTest() {
    // given
    Recipe kimchiStew = Recipe.builder()
            .title("김치찌개")
            .instruction("맛있는 김치찌개를 끓여봅시다!")
            .build();

    // when
    recipeRepository.save(kimchiStew);

    // then
    Recipe findRecipe = recipeRepository.findById(kimchiStew.getId()).get();
    assertThat(findRecipe).isEqualTo(kimchiStew);
    assertThat(findRecipe.getTitle()).isEqualTo("김치찌개");
    assertThat(findRecipe.getInstruction()).isEqualTo("맛있는 김치찌개를 끓여봅시다!");
}

 

주의할 점

- 간결한 엔티티를 작성하되, 테스트 엔티티가 실제 엔티티와 너무 다르면 테스트 결과가 운영 환경에서 제대로 반영되지 않을 수 있다. 간결함과 신뢰성의 균형을 잘 잡자!