단위 테스트는 세 가지 스타일이 있다.
- 출력 기반 테스트 (output-based testing)
- 상태 기반 테스트 (state-based testing)
- 통신 기반 테스트 (communication-based testing)
하나의 테스트에서 하나 또는 둘, 심지어 세 가지 스타일 모두를 함께 사용할 수 있다.
출력 기반 테스트
테스트 대상 시스템(sut)에 입력을 넣고 생성되는 출력을 점검하는 방식이다.
출력 기반 테스트 스타일은 전역 상태나 내부 상태를 변경하지 않는 코드에만 적용되므로 반환 값만 검증하면 된다.

이러한 테스트는 side effect가 없고 sut 작업 결과는 호출자에게 반환하는 값 뿐이다.
public class PriceEngine {
public double calculateDiscount(List<Product> products) {
double discount = products.size() * 0.01;
return Math.Min(discount, 0.2);
}
}
@Test
void 두개의_상품의_할인률을_구한다() {
//given
Product product1 = new Product("Hand wash");
Product product2 = new Product("Hand wash");
List<Product> products = List.of(product1, product2);
PriceEngine sut = new PriceEngine();
//when
double discount = sut.calculateDiscount(products);
//then
Assert.equals(0.02, discount);
}
PriceEngine은 상품 수에 1%를 곱하고 그 결과를 20%로 제한한다.
이 클래스에는 다른 것이 없으며, 내부 컬렉션에 상품을 추가하거나 데이터베이스에 저장하지 않는다.
calculateDiscount() 메서드의 결과는 반환된 할인, 즉 출력 값 뿐이다.

상태 기반 테스트
상태 기반 테스트 스타일은 작업이 완료된 후 시스템 상태를 확인하는 것이다.
이 테스트 스타일에서 상태라는 용어는 sut나 협력자 중 하나, 또는 데이터베이스나 파일 시스템 등과 같은 프로세스 외부 의존성의 상태 등을 의미할 수 있다.

@Getter
public class Order {
private List<Product> products = new ArrayList<>();
public void addProduct(Product product) {
products.add(product);
}
}
@Test
void 주문에_상품을_추가한다() {
//given
Product product = new Product("Hand wash");
Order sut = new Order();
//when
sut.addProduct(product);
//then
Assert.equals(1, sut.getProducts().size());
Assert.equals(product, sut.getProducts().get(0));
}
테스트는 상품을 추가한 후 Products 컬렉션을 검증한다.
출력 기반 테스트의 예제와 달리, addProduct()의 결과는 주문 상태의 변경이다.
통신 기반 테스트
통신 기반 테스트 스타일은 목을 사용해 테스트 대상 시스템(sut)와 협력자 간의 통신을 검증한다.

다음 코드는 통신 기반 테스트의 예를 보여준다.
@Test
void sendingAGreetingsEmail() {
//given
IEmailGateway emailGateway = mock(IEmailGateway.class);
Controller sut = new Controller(emailGateway);
//when
sut.greetUser("jun@email.com");
//then
verify(emailGateway, times(1)).sendGreetingEmail("jun@email.com");
}
스타일에 따른 회귀 방지와 리팩토링 내성
https://ghffu405.tistory.com/53
[단위 테스트] 회귀 방지 (좋은 단위 테스트의 요소)
회귀 : 특정 사건(일반적으로 코드 수정) 후에 기능이 의도한 대로 작동하지 않는 경우다. 소프트웨어 버그와 회귀라는 용어는 동의어이며 바꿔서 사용할 수 있다. 코드베이스가 커질수록 잠재
ghffu405.tistory.com
https://ghffu405.tistory.com/54
[단위 테스트] 리팩토링 내성 (좋은 단위 테스트의 요소)
리팩토링 내성 : 테스트를 "빨간색"(실패)으로 바꾸지 않고 기본 애플리케이션 코드를 리팩토링 할 수 있는지에 대한 척도다. 이러한 상황을 상상해보자.새로운 기능을 개발했으며 모든 것이 잘
ghffu405.tistory.com
회귀 방지와의 관계
회귀 방지 지표는 다음과 같다.
- 테스트 중에 실행되는 코드의 양
- 코드 복잡도
- 도메인 유의성
보통 실행하는 코드가 많든 적든 원하는 대로 테스트를 작성할 수 있다.
어떤 스타일도 이 부분에서 도움이 되지 않는다. 코드 복잡도와 도메인 유의성 역시 마찬가지다.
리팩토링 내성과의 관계
리팩터링 내성은 리팩터링 중에 발생하는 거짓 양성(허위 경보) 수의 대한 척도다.
결국 거짓 양성은 식별할 수 있는 동작이 아니라 코드의 구현 세부 사항에 결합된 테스트의 결과다.
출력 기반 테스트는 테스트가 테스트 대상 메서드에만 결합되므로 거짓 양성 방지가 가장 우수하다.
이러한 테스트가 구현 세부사항에 결합하는 경우는 테스트 대상 메서드가 구현 세부 사항일 때뿐이다.
상태 기반 테스트는 일반적으로 거짓 양성이 되기 쉽다.
이러한 테스트는 테스트 대상 메서드 외에도 클래스 상태와 함께 작동한다.
확률적으로 말하면, 테스트와 제품 코드 간의 결합도가 클수록 유출되는 구현 세부 사항에 테스트가 얽매일 가능성이 커진다.
통신 기반 테스트가 허위 경보에 가장 취약하다.
테스트 대역으로 상호 작용을 확인하는 테스트는 대부분 꺠지기 쉽다.
이는 항상 스텁과 상호작용하는 경우다. 이러한 상호 작용을 확인해서는 안된다.
애플리케이션 경계를 넘는 상호작용(외부 api와 같은 것)을 확인하고,
해당 상호 작용의 side effect가 외부 환경에 보이는 경우에만 목이 괜찮다. (외부 환경에서 보이는 경우는 외부에서 api를 호출해서 결과를 볼 수 있는 경우)
참고
단위테스트 블라디미르 코리코프 지음
'테스트' 카테고리의 다른 글
| 자바의 LocalTime와 MariaDB(MySQL)의 TIME 의 관계 (0) | 2024.07.06 |
|---|---|
| Jpa의 영속성 전이와 테스트의 스프링 트랜잭션 전파 (0) | 2024.06.21 |
| [단위 테스트] 리팩토링 내성 (좋은 단위 테스트의 요소) (0) | 2024.06.17 |
| [단위 테스트] 회귀 방지 (좋은 단위 테스트의 요소) (0) | 2024.06.17 |
| [단위 테스트] 테스트 간의 높은 결합도는 안티 패턴이다. (0) | 2024.06.15 |