클러버

[클러버] @Builder을 활용한 Fixture 셋업 개선

kidmillionaire1998 2025. 3. 17. 15:38

이 글에서는 Spring Boot 테스트 코드에서 Given 절의 Fixture 셋업에서 겪은 고민을 다룬다.

기존 방식

1. 구현

별도의 ServiceTest라는 클래스 파일에서 하나의 @BeforeEach 작업을 통해 모든 테스트 코드의 Fixture을 셋업하고 있었다. Native Query를 활용하여 1. Fixture 객체 생성 2. DB 저장 작업까지 수행한다. Given절에서 반복되는 작업을 하나의 함수에서 담당하게 하기 위함이었다.

 

 

2. 문제

1) 현재 테스트 함수에서 어떤 데이터가 셋업되었는지 명시적으로 알 수 없다

테스트 함수에서 바로 확인할 수 있는 것이 아니라, 매번 셋업 함수(@BeforeEach)에서 어떤 데이터가 셋업되었는 지 확인해봐야 한다.

 

2) 테스트 함수를 독립적으로 수행하기 어렵다

하나의 셋업 함수를 통해 모든 테스트 셋업을 담당하다보니 해당 테스트 케이스와 관계 없는 데이터까지 셋업되게 된다. 셋업 함수의 분리가 필요해보였다.

 

목표

1. 가시성

테스트 함수만 보더라도 셋업된 Fixture의 특징이 명확하게 드러나도록 구현하고 싶었다.
예를 들어, 간단한 데이터 수정 작업을 수행하는 테스트의 경우에도 셋업된 Fixture의 필드 값과 Request에 포함된 수정하려는 필드 값이 서로 다름이 명확하게 보이도록 해야할 것이다.

 

 

2. 독립성

하나의 함수에서 모든 테스트 함수의 셋업을 담당하다보니, 테스트의 독립성이 떨어지는 문제가 있었다. 모든 함수에서 테스트 셋업을 독립적으로 수행하되, 중복된 코드를 최소화하고자 하였다. 

 

해결 과정 & 새로운 문제

매 함수마다 빌더 함수를 활용하여 셋업할 수 있었으나, 이는 중복된 코드가 너무 많다고 생각하였다. 대신, 각 테스트 클래스에서 Fixture을 반환하는 팩토리 함수(static 메서드 이용)를 구현하고자 하였다.

 

하지만, 함수에 전달하는 파라미터 개수가 많아질수록 전체적인 코드 양과 중복되는 코드가 증가했고, 이로 인해 테스트 함수의 관심사인 데이터를 명확히 확인하기 어려웠다. 앞에서 언급한 예시를 다시 가져온다면, 수정하려는 데이터가 명확히 어떤 필드인지 알 수가 없었다.

 

해결 방안 목록

1. Default Parameter 

함수에 Default Parameter을 지정하고, 필요한 파라미터만 셋업하여 Fixture 생성 함수를 호출하는 방식이다. Kotlin, Javascript 같은 경우에는 이를 지원하기 때문에, 현재 내가 사용하고 있는 Java 언어에서는 default parameter가 지원되지 않아서 적용하기 힘들었다.

 

2. Fixture 생성 함수 오버라이딩

필요한 파라미터만 포함하는 함수를 매번 만들게 되면 너무 많은 함수가 필요해진다. 구현 부분에서의 불필요한 고민(어떤 함수를 호출해야할 지 명확히, 해당 함수의 네이밍을 어떻게 하는 지 등)이 추가적으로 생긴다고 생각했다. 

 

3. @Builder 활용

@Builder을 통해 각 필드에 대한 빌더 함수를 호출하여 값을 세팅할 수 있다. 모든 필드가 아닌, 테스트 함수에서 명시적으로 셋팅이 필요한 필드값만 세팅도 가능하기 때문에 해당 방법을 통해 세팅하기로 했다.

 

개선된 방식 

1) Default로 세팅된 Fixture Builder 객체 반환 : 공통 Fixture 클래스

 

예시는 JPA Entity 객체를 셋팅하는 코드이다. (현재 프로젝트에서는 모든 JPA Entity에 @Builder가 적용되어 있다.)

기본 Fixture는 말 그대로 '기본값'을 통해 셋업되는 것이며, 이때 어떤 값으로 생성되었는지는 테스트의 관심사가 아니기 때문에,
테스트 함수와 같은 클래스에 위치할 필요가 없다. 따라서 Fixture 생성을 위한 코드는 별도의 클래스로 분리하였고 모든 테스트 코드에서 호출 가능한 공통 Fixture 클래스에 구현하였다. (도메인 별로 분리하였다. ex) AdminFixture)

 

2) 각 필드에 대한 Builder 함수 호출  : 각 테스트 함수

앞에서 Default Builder를 반환한 후, 테스트 함수에 세팅된 주요 관심사 필드의 Builder 함수를 호출하여 값을 구성하고 최종 Fixture 객체를 완성한다. 기본 인자를 덮어씌운다고 생각하면 편하다.

 

위 테스트 코드는 비밀번호 변경을 수행하는 테스트 코드이다. 이 테스트의 주요 관심사는 "password" 필드 하나이며, Given 절에서는 기존 비밀번호인 oldPassword로 저장된다는 점을 명확히 보여준다. 또한, Request DTO의 newPassword 필드에 기존의 oldPassword 값을 그대로 설정함으로써,실제 비밀번호 변경 시 이전과 동일한 비밀번호로 요청되었음이 명확히 드러난다.

 

느낀점 & 개선점

 

1) 코드 양을 줄이긴 했지만, 여전히 빌더 함수를 호출하는 과정에서 필요한 코드 양은 많다. (default parameter를 활용하는 방법이 훨씬 짧고 명확한 거 같다.)

 

2) 테스트 함수에서 셋업될 필요가 없는 도메인의 Fixture인 경우 별도의 SQL 파일을 통해 셋업하는 방법으로 분리할 생각이다. 

 

3) Request DTO Set Up 

현재 Request DTO에서는 역직렬화되기 위해 Builder가 필요가 없다. 테스트 코드를 위해 어플리케이션 코드가 변경되는 것은 잘못되었다고 생각하여서, Fixture Monkey를 이용한 Builder Set up방식을 통해 구현하였는데, 해당 방법이 과연 최선의 방법인가 고민중이다.

 

 

 

참고 

https://jojoldu.tistory.com/611

 

테스트 픽스처 올바르게 사용하기

xUnit에서는 테스트 대상 시스템 (System Under Test, 이하 SUT) 를 실행하기 위해 해줘야 하는 모든 것을 테스트 픽스처라고 부른다. 처음 테스트 코드를 배우게 되면 이 테스트 픽스처 부분에 대해서

jojoldu.tistory.com

 

https://tech.kakaopay.com/post/given-test-code-2/#%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%A7%80%EC%98%A5

 

실무에서 적용하는 테스트 코드 작성 방법과 노하우 Part 3: Given 지옥에서 벗어나기 - 스노우볼을

테스트 코드 Given 절 작성의 어려움을 해결하고 재사용성을 극대화하는 전략을 소개합니다.

tech.kakaopay.com


https://github.com/eternity-oop/DDD-essence-part2/blob/main/src/test/java/org/eternity/food/Fixtures.java

 

DDD-essence-part2/src/test/java/org/eternity/food/Fixtures.java at main · eternity-oop/DDD-essence-part2

도메인 주도 설계의 사실과 오해 파트 2 예제. Contribute to eternity-oop/DDD-essence-part2 development by creating an account on GitHub.

github.com