[유닛테스트] 단위테스트 스타일
요약
단위테스트의 스타일은 3가지로 나뉜다.
- 상태 기반
- 출력 기반
- 통신 기반
여기서 출력 기반 스타일의 테스트는 가장 품질이 좋지만 모든 코드에 적용할 수 없고 순수 함수 방식으로만 작성된 코드에 사용할 수 있다.
통신 기반 테스트는 간혈적으로 사용해야 한다.
단위 테스트의 세 가지 스타일
1. 출력 기반 테스트

SUT에 입력을 넣고 생성되는 출력을 검증하는 방식이다.
- 전역 상태나 내부 상태를 변경하지 않는 코드에만 적용된다.
- 함수형(functional)이라고 한다.
- 함수형 프로그래밍 에 뿌리를 두고 있다.
2. 상태 기반 스타일 정의

작업이 완료된 후 시스템 상태를 확인하는 것이다.
상태 기반 스타일에서 상태란 SUT나 협력자 중 하나, 또는 DB나 파일시스템과 같은 프로세스 외부 의존성의 상태를 의미할 수 있다.
예제
class Order{
private val _products: MutableList<Product> = mutableListOf()
private val products: List<Product>
get() = _products
fun addProduct(product: Product){
_products.add(product)
}
}
@Test
fun `Adding a product to an order`(){
val product = Product("Hand wash")
val sut = Order();
sut.addProduct(product);
Assertions.assertEquals(1, sut.products.count())
Assertions.assertEquals(product, sut.products[0])
}
addProduct를 검증하는게 아닌 Order의 상태값인 products를 검증했다.
3. 통신 기반 스타일 정의
목을 사용해 테스트 대상 시스템과 협력자 간의 통신을 검증한다.
@Test
fun `Sending a greetings eamil`(){
val emailGatewayMock: EmailGateWay = mockk()
val sut = new Controller(emailGatewayMock)
val email = "user@email.com"
every{
emailGatewayMock.sendGreetingsEmail(email)
} just run
sut.greetUser(email)
verify(exactly = 1){
emailGatewayMock.sendGreetingsEmail(email)
}
}
단위테스트 스타일 비교
| | 출력 기반 | 상태 기반 |통신 기반| |—————————–|———|———|——| |리펙터링 내성을 지키기 위해 필요한 노력| 낮음 | 중간 | 중간 | |유지비 | 낮음 | 중간 | 높음 |
결론
출력 기반 테스트를 선호하자.
- 코드를 순수함수로 바꾸면 출력 기반 테스트로 변경할 수 있다.
- 하지만 모든 코드를 순수 함수로 바꿀 수 없다.
함수형 아키텍처

함수형 프로그래밍은 수학적 함수(mathematial function)혹은 순수 함수(pure function)이라고 불리우는 함수를 사용하는 프로그래밍이다.
- 하나의 입력과 하나의 출력이 있으며 둘 다 메서드 시그니처에 명시되어 있다.
- 숨은 입출력이 없는 메서드이다.
fun decimal calculatorDiscount(products: Array<Product>){
val discount: BigDecimal = products.length * 0.01
return Math.min(discount, 0.2)
}
비즈니스 로직과 부수효과를 분리하는 방법
두 가지 코드 유형을 구분하여 분리할 수 있다,.
- 결정을 내리는 코드: 해당 코드는 부작용이 없기 때문에 수학적 함수를 사용해 작성할 수 있다.
- 함수형 코어
- 해당 결정에 따라 작용하는 코드: 이 코드는 수학적 함수에 의해 이뤄진 모든 결정을 데이터베이스의 변경이나 메시지 버스로 전송된 메시지와 같이 가시적인 부분으로 변환한다.
- 가변 셸