Styles-Of-Unit-Testing
1 단위 테스트 스타일
1.1 단위 테스트의 세 가지 스타일
- 단위 테스트에는 세 가지 스타일이 있다
- 출력 기반 테스트
- 상태 기반 테스트
- 통신 기반 테스트
- 출력 기반 테스트, 상태 기반 테스트, 통신 기반 테스트 순으로 테스트 품질이 좋다
- 출력 기반 테스트가 가장 품질이 좋지만 아무데서나 사용할 수 없으며 순수 함수 방식으로 작성된 코드에만 적용된다
- 그러나 걱정하지 마라 출력 기반 스타일로 변환하는 데 도움이 되는 기법이 있다
1.2 스타일과 단위 테스트 분파
- 두 분파 모두 출력 기반 테스트를 사용한다
- 고전파는 통신 기반 테스트보다 상태 기반 테스트를 선호한다
- 런던파는 상태 기반 테스트보다 통신 기반 테스트를 선호한다
2 출력 기반 스타일
- SUT에 입력을 넣고 생성되는 출력을 점검하는 방식
- 출력 기반 스타일은 전역 상태나 내부 상태를 변경하지 않는 코드에만 적용되므로 반환 값만 검증하면 된다
- 전역 상태나 내부 상태를 변경하지 않는 코드 -> 순수 함수 방식으로 작성된 코드
- 출력 기반 단위 테스트 스타일을 함수형이라고도 한다
- 부작용이 없는 코드 선호를 강조하는 프래그래밍 방식인 함수형 프로그래밍에 뿌리를 두고있다
[!NOTE] 순수 함수란? 전역 상태나 내부 상태를 변경하는 부작용이 없는 코드로 작업의 결과는 호출자에게 반환하는 값뿐이다. 오직 입력만이 결과에 영향을 주는 함수를 말한다.
PriceEngine.java
public class PriceEngine {
public double calculateDiscount(List<Product> products) {
double discount = products.size() * 0.01;
return Math.min(discount, 0.2);
}
}
- PriceEngine 클래스의 calculateDiscount() 메서드를 출력 기반 스타일로 테스트해보자
- calculateDiscount 메서드는 할인율을 계산한다
- 상품의 수에 1%를 곱하고 그 결과를 20%로 제한한다
- calculateDiscount 메서드는 순수 함수다
- 다른 가변 상태를 참조하지 않고 함수 스스로도 다른 상태를 변경하지 않는다. 즉 부작용이 없다
- 내부 컬렉션에 상품을 추가하거나 데이터베이스에 저장하지 않는다.
- 따라서 출력 기반 스타일로 테스트가 가능하다
출력 기반 스타일 테스트
@Test
void discount_of_two_products() {
// Arrange
List<Product> products = Arrays.asList(new Product("A"), new Product("B"));
Order sut = new Order();
// Act
double discount = sut.calculateDiscount(products);
// Assert
assertThat(discount).isEqualTo(0.02);
}
- calculateDiscount 메서드는 부작용이 없는 순수 함수이기 때문에 검증 구절에서 반환 값만 검증하고 있다.
3 상태 기반 스타일
- 상태 기반 스타일은 작업이 완료된 후 시스템 상태를 확인하는 것이다
- 검증할 수 있는 상태의 종류
- SUT의 상태
- 협력자의 상태
- 프로세스 외부 의존성의 상태(데이터베이스, 파일 시스템 등)
Order.java
@Getter
public class Order {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
}
- Order 클래스의 addProduct 메서드는 순수 함수가 아니므로 출력 기반 스타일 테스트코드를 작성할 수 없다
- 메서드 외부 가변 상태(products)를 변경하기 때문에 순수 함수가 아니다
- 따라서 상태 기반 테스트를 작성해야 한다
상태 기반 스타일 테스트
@Test
void adding_a_product_to_an_order() {
// Arrange
Product product = new Product("A");
Order sut = new Order();
// Act
sut.addProduct(product);
// Assert
assertThat(sut.getProducts().size()).isEqualTo(1);
assertThat(sut.getProducts().get(0)).isEqualTo(product);
}
- 상태 기반 스타일 작업 완료후 시스템 상태를 확인한다
4 통신 기반 스타일
- SUT의 협력자를 목으로 대체하고 SUT가 협력자를 올바르게 호출하는지 검증한다
통신 기반 스타일 테스트
@Test
void sending_a_greetings_email() {
// Arrange
EmailGateWay emailGateWay = mock(EmailGateWay.class);
EmailController sut = new EmailController(emailGateWay);
// Act
sut.greetUser("user@email.com");
// Assert
verify(emailGateWay, times(1)).sendGreetingsEmail("user@email.com");
}
- EmailController의 협력자인 EmailGateWay를 목으로 대체
- 검증구절에서 EmailGateWay의 sendGreetingsEmail 메서드를 정확히 한번 호출했는지 검증한다