향로님의 기억보단 기록을 티스토리 블로그의 글을 Java 코드로 각색하여 재구성한 내용입니다.
Test Fixture 사용 예시
💡예시
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.time.LocalDateTime;
import static org.junit.jupiter.api.Assertions.*;
class OrderTest {
private Order sut;
@BeforeEach
void setUp() {
sut = Order.create(1000, LocalDateTime.of(2021, 10, 30, 10, 0, 0), "배민주문");
}
@Test
void 주문취소1() {
Order cancelOrder = sut.cancel(LocalDateTime.of(2021, 10, 31, 0, 0, 0));
assertEquals(OrderStatus.CANCEL, cancelOrder.getStatus());
assertEquals(-1000, cancelOrder.getAmount());
assertEquals("배민주문", cancelOrder.getDescription());
}
}
- @BeforeEach 를 통해 해당 파일 혹은 클래스 내부의 함수(혹은 메소드) 들이 시작하기전에 항상 수행하는 일을 지정할 수 있다.
문제점
💡문제점 1
- 첫번째 문제는 테스트가 무엇을 전제로 동작하는지 한 눈에 알수가 없다는 점이다.
- 테스트 메서드를 제대로 확인하려면 setup 함수의 코드에서 fixture 코드를 전부 확인해야 한다.
- 어떤 값을 가진 객체가 만들어진 것인지
- 어떤 함수가 어떻게 Mock / Stub 처리 되었는지 등
- setup을 통해 픽스처 구성을 허락하면 극단적으로는 다음과 같은 테스트 코드를 만나기도 한다.
assertEquals(-1000, sut.cancel(LocalDateTime.of(2021, 10, 31, 0, 0, 0)).getAmount());
- 테스트 메서드는 그 하나로 완전한 프로그램이 되어야 한다.
- 절대 테스트 메서드를 이해하기 위해 다른 부분을 찾아보게 만들면 안된다. (출처)
💡문제점 2
- 두번째 문제는 Test Fixture 코드 하나만 변경해도 모든 테스트에 영향을 미친다는 점이다.
- 예를 들어 아래와 같은 setup 메서드를 구성하고 있을때,
order.addPay (Pay.of(10000));
- 아래처럼 코드가 변경된다면?!
order.addPay (Pay.of(15000));
- setup 을 통하는 모든 테스트 메서드들이 10,000원이 15,000원으로 변경되어도 기존 테스트 코드에 문제가 없다는 것을 다 검증해봐야한다.
- 좋은 테스트의 기본 조건은, 테스트를 수정해도 다른 테스트에 영향을 주지 않고 각각의 테스트 메서드들 간에 격리되어있는 것이다.
- Test Fixture 또한 높은 결합도를 가지면 안된다.
💡해결
setup 메서드를 통해 Test Fixture를 고정하게되면 위와 같은 문제점이 있을 때, 자주 사용하게되는 Test Fixture를 어떻게 하는 것이 좋을까?
1. 클래스 내부에 private 팩토리 메소드를 만들어서 사용
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
class Order2Test {
@Test
void 주문취소1() {
int amount = 1000;
String description = "배민주문";
Order sut = createOrder(amount, description);
Order cancelOrder = sut.cancel(
LocalDateTime.of(2021, 10, 31, 0, 0, 0)
);
assertEquals(OrderStatus.CANCEL, cancelOrder.getStatus());
assertEquals(-amount, cancelOrder.getAmount());
assertEquals(description, cancelOrder.getDescription());
}
@Test
void 주문취소2() {
int amount = 1000;
Order sut = createOrder(amount);
int canceledAmount = sut.cancel(
LocalDateTime.of(2021, 10, 31, 0, 0, 0)
).getAmount();
assertEquals(-amount, canceledAmount);
}
private Order createOrder(int amount) {
return createOrder(amount, "배민주문");
}
private Order createOrder(int amount, String description) {
return Order.create(
amount,
LocalDateTime.of(2021, 10, 30, 10, 0, 0),
description
);
}
}
- 여기서는 테스트 환경을 의도적으로 구성할 수 있도록 createOrder(amount, description) 혹은 createOrder(amount) 와 같이 테스트에 사용되는 값만 설정한다.
- 테스트에 필요하지 않은 값들은 기본 값들로 구성한다. ex. LocalDateTime.of(2021, 10, 30, 10, 0, 0)
2. 클래스 외부에 static 팩토리 메소드를 만들어서 사용
import java.time.LocalDateTime;
/**
* 테스트에서만 사용하는 Order 생성 전용 팩토리
*/
public final class TestOrderFactory {
private static final LocalDateTime DEFAULT_ORDERED_AT =
LocalDateTime.of(2021, 10, 30, 10, 0, 0);
private static final String DEFAULT_DESCRIPTION = "배민주문";
private TestOrderFactory() {
// 인스턴스 생성 방지
}
public static Order create() {
return create(1000);
}
public static Order create(int amount) {
return create(amount, DEFAULT_DESCRIPTION);
}
public static Order create(int amount, String description) {
return Order.create(
amount,
DEFAULT_ORDERED_AT,
description
);
}
}
3. 팩토리 클래스로 추출해서 사용
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.LocalDateTime;
import org.junit.jupiter.api.Test;
class Order2Test {
@Test
void 주문취소1() {
int amount = 1000;
String description = "배민주문";
Order sut = TestOrderFactory.create(amount, description);
Order cancelOrder = sut.cancel(
LocalDateTime.of(2021, 10, 31, 0, 0, 0)
);
assertEquals(OrderStatus.CANCEL, cancelOrder.getStatus());
assertEquals(-amount, cancelOrder.getAmount());
assertEquals(description, cancelOrder.getDescription());
}
@Test
void 주문취소2() {
int amount = 1000;
Order sut = TestOrderFactory.create(amount);
int canceledAmount = sut.cancel(
LocalDateTime.of(2021, 10, 31, 0, 0, 0)
).getAmount();
assertEquals(-amount, canceledAmount);
}
}
- 여러 곳에 사용될 수 있는 Test Fixture 라면, 별도의 TestOrderFactory와 같이 팩토리 클래스를 추출해서 사용하는 방법도 있다.
참고 자료
https://jojoldu.tistory.com/611
테스트 픽스처 올바르게 사용하기
xUnit에서는 테스트 대상 시스템 (System Under Test, 이하 SUT) 를 실행하기 위해 해줘야 하는 모든 것을 테스트 픽스처라고 부른다. 처음 테스트 코드를 배우게 되면 이 테스트 픽스처 부분에 대해서
jojoldu.tistory.com
https://velog.io/@langoustine/Test-Fixture
테스트 픽스처(Test Fixture)를 어떻게 만드는 것이 좋은 걸까?
이번 글에서는 테스트의 독립성을 지키기 위해 테스트 케이스마다 Fixture를 만드는 것과 테스트 케이스마다 중복으로 발생하는 Fixture를 setUp 등으로 통합하는 것 중에서 어떤 방법을 사용해야 하
velog.io
'심화 > TDD' 카테고리의 다른 글
| [TDD] 일관된 테스트 결과를 위한 Test Fixture 사용하기 (0) | 2025.12.31 |
|---|---|
| [TDD] 통합 테스트 (Java, SpringBoot) (0) | 2025.12.05 |
| [TDD] 좋은 테스트 코드의 조건 (0) | 2025.12.04 |