What-Is-A-Unit-Test
1 단위 테스트란 무엇인가?
- 단위 테스트의 정의에는 많은 미묘한 차이들이 있다
- 해석에 차이에 따라 고전파와 런던파로 두 가지로 나뉜다
1.1 단위 테스트 정의
- 단위 테스트에는 많은 정의가 있지만 중요한 것만 추리면 아래와 같다
- 작은 코드 조각을 검증하고
- 빠르게 수행하고
- 격리된 방식으로 처리하는 자동화된 테스트다
- 첫 번째 속성은 논란의 여지가 없다지만 정확히 빠르다는 것은 주관적인 척도이므로 논쟁이 있을 수 있지만 그리 중요하지 않다
- 테스트 스위트의 실행 시간이 충분하다면 테스트가 충분히 빠르다는 의미다
- 대중의 의견이 갈리는 것은 세 번째 속성이다.
- 고전파와 런던파의 견해가 이 속성에서 갈린다
1.2 고전파와 런던파
- 단위 테스트의 세 번째 속성에서 격리가 정확히 무엇인지에 대한 의견 차이로 고전파와 런던파로 나누어진다
1.3 런던파의 단위 테스트 정의
런던파의 단위 테스트는
- 단일 클래스 내의 메서드를 검증하며
- 빠르게 수행하고
- 테스트 대상을 협력자로부터 격리하는 자동화된 테스트
1.4 고전파의 단위 테스트 정의
고전파의 단위 테스트는
- 단일 동작 단위를 검증하며
- 빠르게 수행하고
- 다른 테스트와 별도로(격리하여) 처리하는 자동화된 테스트
2 런던파의 단위 테스트
2.1 격리 문제에 대한 런던파 접근
- 런던파가 생각하는 격리란 아래와 같다
- 테스트 대상 시스템(SUT)을 협력자에게서 격리하는 것
- 즉 하나의 클래스가 다른 클래스 또는 여러 클래스에 의존하면 이 모든 의존성을 테스트 대역으로 대체해야 한다
- 불변 의존성은 제외한 모든 의존성을 대체함
- 그래서 런던파를 목 추종자라고 부른다
- 테스트 대역은 Test-Double.md 참고
2.2 런던파 접근의 장점
테스트가 실패하면 코드베이스의 어느 부분이 고장 났는지 확실히 알 수 있다
- 테스트가 실패하면 테스트 대상 시스템이 고장난 것이다
- 테스트 대상 시스템 클래스의 모든 의존성은 테스트 대역으로 대체됐기 때문에 의심할 여지가 없다
객체 그래프를 분할할 수 있어 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다
- 실제 협력자 대신해 목을 사용하면 클래스를 쉽게 테스트할 수 있다
- 특히 SUT 클래스에 의존성이 있고 이 의존성에 다시 각각의 의존성이 있는 복잡한 객체 그래프가 있을 때 쉽게 테스트할 수 있다
- 객체 그래프: 같은 문제를 해결하는 클래스의 통신망
- 테스트 대역을 쓰면 클래스의 직접적인 의존성을 대체해 객체 그래프를 나눌 수 있다
- 단위 테스트에서 준비해야 할 작업량을 크게 줄일 수 있다
- 고전파의 단위 테스트는 SUT를 설정하려면 전체 공유 의존성을 제외한 전체 객체 그래프를 다시 생성해야 한다
- 고전파의 설정이 많은 작업량을 가지고 있어 위는 사실이지만 복잡한 객체 클래스를 설정해 테스트할 방법을 찾는 대신 근본적으로 복잡한 클래스 그래프를 갖 지 않는 데 집중해야 한다
- 대게 클래스 그래프가 커진 것은 코드 설계의 문제다
간단한 테스트 스위트 구조
- 프로젝트 전반적으로 한 번에 한 클래스만 테스트하라는 지침을 도입하면 전체 단위 테스트 스위트를 간단한 구조로 할 수 있다
- 더 이상 코드베이스를 테스트하는 방법을 고민할 필요가 없다
- 클래스가 있다면 클래스에 해당하는 단위 테스트 클래스를 생성하면 된다
2.3 런던파 접근의 단점
과도한 명세 문제
- 런던 스타일이 고전 스타일 테스트보다 테스트가 구현에 더 자주 결합되는 편이다
- 테스트는 단일 동작 단위를 검증해야 한다
- 이보다 작은 단위로 검증하게 되면 이 테스트가 무엇을 검증하는지 정확히 이해하기가 어려워 진다
- 단일 동작 단위를 더 작은 단위로 검증하는 것의 차이는 비유하자면
우리집 강아지를 부르면, 바로 나에게 온다
와우리집 강아지를 부르면 먼저 왼쪽 앞다리를 움직이고, 이어서 오른쪽 앞다리를 움직이고, 머리를 돌리고, 꼬리를 흔들기 시작한다
의 차이와 같다 - 실제 동작(개가 주인에게 오는 것) 대신 개별 클래스(다리, 머리, 꼬리)를 목표로 테스트하면 실제 동작을 테스트 하는 건지 이해하기 힘들다
- 개별 클래스를 목표로 테스트 하면 강아지가 나에게 오고 있는가? 아니면 도망을 가고 있는가? 알 수 없는 테스트가 된다
테스트는 단일 동작 단위를 검증해야 한다
테스트는 코드의 단위를 검증해서는 안 된다. 오히려 동작의 단위, 즉 문제 영역에 의미가 있는 것, 이상적으로 비즈니스 담당자가 유용하다고 인식할 수 있는 것을 검증해야한다. 동작 단위를 구현하는 데 클래스가 얼마나 필요한지는 상관없다. 단위는 여러 클래스에 걸쳐 있거나 한 클래스에만 있을 수 있고, 심지어 아주 작은 메서드가 될 수 있다
2.4 런던파 테스트 예시
- 온라인 상점을 운영한다고 가정하고 상점에 재고가 충분하면 구매는 성공으로, 구매 수량만큼 제품 수량이 줄어든다
- 제품이 충분하지 않으면 구매는 성공하지 못하며 상점에는 아무 일도 일어나지 않는다
AAA Pattern 런던파 테스트 코드
- AAA Pattern은 The-Anatomy-Of-A-Unit-Test.md를 참고
- 준비 단계
- 테스트는 Store의 실제 인스턴스를 생성하지 않고 Mock 객체를 대신 사용한다
- 샴푸 재고를 추가해 Stroe의 상태를 수정하는 대신 hasEnoughInventory() 메서드를 호출에 어떻게 응답하는지 목에 직접 정의한다
- 검증 단계
- Customer의 purchase 호출 결과를 검증한다
- 추가적으로 지금은 Customer와 Stroe 간의 상호 작용을 검사한다
- 즉 Customer가 Stroe를 올바르게 호출했는지 검증한다
@Test
void purchase_succeeds_when_enough_inventory_mock() {
// Arrange
Store storeMock = mock(Store.class);
given(storeMock.hasEnoughInventory(Product.Shampoo, 5))
.willReturn(true);
Customer customer = new Customer();
// Act
boolean success = customer.purchase(storeMock, Product.Shampoo, 5);
// Assert
assertThat(success).isTrue();
verify(storeMock, times(1)).hasEnoughInventory(Product.Shampoo, 5);
}
3 고전파의 단위 테스트
3.1 격리 문제에 대한 고전파 접근
- 고전파가 생각하는 격리란 단위 테스트를 서로 격리하는 것이다
- 단위 테스트를 서로 격리한다는 의미는 단위 테스트가 서로 병렬로 실행될 수 있음을 의미한다
- 여러 클래스가 모두 메모리에 상주하고 공유 상태에 도달하지 않는 한 여러 클래스를 한 번에 테스트해도 괞찮다