3장 단위 테스트 구조
날짜
2024년 4월 11일
단위 테스트를 구성하는 방법
모든 단위 테스트는 AAA패턴을 따라야 한다. 테스트 내 준비나 실행 또는 검증 구절이 여러 개 있으면, 테스트가 여러 동작 단위를 한 번에 검증한다는 표시다. 이 테스트가 단위 테스트라면 각 동작에 하나씩 여러 개의 테스트로 나눠야 한다.
AAA패턴 : (Arrange/Act/Assert)
준비 구절 : 테스트 대상 시스템 (SUT, System Under Test)과 해당 의존성을 원하는 상태로 만든다.
실행 구절 : SUT에서 메서드를 호출하고 준비된 의존성을 전달하며 출력 값을 캡처한다.
검증 구절 : 결과를 검증한다.
TDD를 실천할 대 검증 구절부터 작성할 수 있지만, 그 외에는 준비 구절부터 시작하는 것이 좋다.
테스트 내 if 문은 너무 많은 것은 검증한다는 표시이므로 피한다.
준비 구절이 클 경우 : 비공개 메서드, 팩토리 클래스, 오브젝트 마더, 테스트 데이터 빌더
실행구절이 클 경우
캡슐화를 깨는 것이므로 코드 공개 API에 문제가 있을 수 있는 것
단일 작업을 수행하는 데 두 개의 메서드 호출이 필요하다. 비즈니스 관점에서 구매가 정상적으로 이뤄지면 고객의 제품 획득, 매장 재고 감소라는 두 가지 결과가 만들어진다. 이는 함께 동작해야지, 클라이언트에게 불필요한 메서드 호출을 더 강요해서는 안된다.
해결책은 코드에서 purchase 내에 removeInventory를 넣어 클라이언트 코드에 의존하지 않게 한다.
C#
복사
// 실행
bool success = customer.purchase(store, Product.Shampoo, 5);
store.removeInventory(success, Product.Shampoo, 5);
검증 구절이 클 경우
단위 테스트의 단위는 동작의 단위이므로 여러 결과를 낼 수 있다.
다만 추상화가 누락됐을 수 있다.
모든 속성을 검증하는 대신 객체 클래스 내에 동등멤버를 정의하는 것이 좋다.
실행 구절이 한 줄 이상이면 SUT의 api에 문제가 있다는 뜻이다. 클라이언트가 항상 이러한 작업을 같이 수행해야 하고, 이로 인해 잠재적으로 모순으로 이어질 수 있다. 이러한 모순을 불변 위반이라고 한다. 잠재적인 불변 위반으로부터 코드를 보호하는 것을 캡슐화라고한다.
xUnit jUnit5 테스트 프레임워크 살펴보기
[Fact] → @Test
[SetUp] → @BeforeEach
[TearDown] → @AfterEach
@BeforeEach / @AfterEach: 각 테스트가 실행되기 전후에 실행될 메소드를 지정
@BeforeAll / @AfterAll: 모든 테스트가 실행되기 전후에 단 한 번씩 실행될 메소드를 지정
@Disabled: 특정 테스트를 비활성화하고자 할 때 사용
@Nested: 중첩된 테스트 클래스를 정의할 때 사용하여 테스트 사례의 계층 구조를 만듬
@ParameterizedTest: 다양한 매개변수를 사용하여 여러 번 테스트를 실행할 수 있게 함
테스트 간 테스트 픽스처 재사용
테스트 픽스처
1. 테스트 픽스처는 테스트 실험 대상 객체다. SUT로 전달되는 인수다. 데이터베이스에 있는 데이터나 하드 디스크의 파일일 수도 있다. 이러한 객체는 각 테스트 실행 전에 고정 상태로 유지하기 때문에 동일한 결과를 생성한다. 따라서 픽스처라는 단어가 나왔다.
안 좋은 방법
SetUp(@BeforeEach) 또는 생성자에서 픽스처 초기화
→ 단점 : 테스트 간 결합도가 높아진다.
테스트 준비 로직을 수정하면 다른 테스트에도 영향을 미친다.
이는 다른 테스트에 영향을 주어서는 안된다는 중요한 지침을 위반한다.
더 나은 방법
비공객 팩터토리 메서드를 둔다.
Java
복사
public class CustomerTests {
@Test
public void purchase_succeeds_when_enough_inventory() {
Store store = createStoreWithInventory(Product.Shampoo, 10);
Customer sut = CreateCustomer();
boolean success = sut.purchase(store, Product.Shampoo, 5);
assertTrue(success);
assertEquals(5, store.getInventory(Product.Shampoo));
}
@Test
public void purchase_fails_when_enough_inventory() {
Store store = createStoreWithInventory(Product.Shampoo, 10);
Customer sut = createCustomer();
boolean success = sut.purchase(store, Product.Shampoo, 15);
assertFalse(success);
assertEquals(10, store.getInventory(Product.Shampoo));
}
private Store createStoreWithInventory(Product product, int quantity) {
Store store = new Store();
store.addInventory(product, quantity);
return store;
}
private Customer createCustomer() {
return new Customer();
}
}
오브젝트 마더 패턴
테스트 픽스처 초기화 코드는 생성자에 두지 말고 팩토리 메서드를 도입해서 재사용하자. 이러한 재사용은 테스트 간 결합도를 상당히 낮게 유지하고 가독성을 향상시킨다.
단위 테스트 명명법
단위 테스트의 단위는 동작이기 때문에 엄격한 명명 정책을 따르지 말고 다음 지침을 따르자.
문제 도메인에 익숙한 비개발자들이 이해하기 좋게 짓자.
단어를 밑줄로 구분하자.
IsDeliveryValid_InvalidDate_ReturnsFalse
→
Delivery_with_a_past_date_is_invalid
매개변수화된 테스트 리팩터링하기
비슷한 동작을 단일한 테스트 메서드로 묶을 수 있다.
Delivery_with_a_past_date_isValid()
Delivery_for_today_is_invalid()
Delivery_for_tomorrow_is_invalid()
The_soonest_delivery_date_is_two_days_from_now()
위 네 가지 테스트 메스드의 유일한 차이점은 배송 날짜뿐이다.
입력 매개변수만으로 테스트 케이스를 판단할 수 있다면 부정,긍정 케이스를 묶는 것이 좋다.
그렇지 않으면 분리하라.
Java
복사
public class DeliveryServiceTests {
//부정적인 테이스 단순화
@ParameterizedTest
@ValueSource(ints = [-1, 0, 1]) //테스트 케이스
public void can_detect_an_invalid_delivery_date(int daysFromNow) {
DeliveryService sut = new DeliveryService();
DateTime deliveryDate = DateTime.now().plusDays(daysFromNow.toLong());
Delivery delivery = new Delivery(deliveryDate);
boolean isValid = sut.isDeliveryValid(delivery);
assertFalse(isValid);
}
//긍정적인 테스트 케이스는 고유한 테스트로 도출
@Test
public void the_soonest_delivery_date_is_two_days_from_now() {
/* ... */
}
}
매개변수화된 테스트를 위한 데이터 생성
DateTime.now() 메서드는 런타임에 의존하여 @DataSource 어노테이션에 넣으면 컴파일 에러가 발생할 것이다.
테스트 메서드에 공급할 사용자 정의 데이터를 생성하는 데 사용되는 @MethodSource 어노테이션을 사용하면 컴파일러의 제한을 극복하고, 매개변수화된 테스트에서 모든 유형의 매개변수를 사용할 수 있다.
Java
복사
@ParameterizedTest
@MethodSource("data")
public void can_detect_an_invalid_delivery_date(
DateTime deliveryDate,
bool expected) {
/* .. */
}
public static List<Object[]> data() {
return new List<Object[]> {
new Object[] { DateTime.now().plusDays(-1), false },
new Object[] { DateTime.now(), false },
new Object[] { DateTime.now().plusDays(1), false },
new Object[] { DateTime.now().plusDays(2), true },
}
}
검증문 라이브러리를 사용한 테스트 가독성 향상
검증문 라이브러리를 사용하면, 쉬운 영어처럼 읽도록 검증문에서 단어 순서를 재구성해 테스트 가독성을 더욱 향상시킬 수 있다.
표
'책 > 단위 테스트' 카테고리의 다른 글
[단위 테스트] 7장 가치 있는 단위 테스트를 위한 리팩터링 (0) | 2025.03.18 |
---|---|
[단위 테스트] 6장 단위 테스트 스타일 (0) | 2025.03.18 |
[단위 테스트] 5장 목과 테스트 취약성 (0) | 2025.03.18 |
[단위 테스트] 1장 단위 테스트의 목표 (0) | 2025.03.18 |