•
2장에서 다루는 내용
◦
단위 테스트란?
◦
공유 의존성, 비공개 의존성, 휘발성 의존성 간의 차이점
◦
단위 테스트의 두 분파: 고전파와 런던파
◦
단위 테스트, 통합 테스트, E2E 테스트의 차이점
•
단위 테스트에는 고전파와 런던파 두 가지 견해가 있다.
◦
고전파는 원론적으로 접근하는 방식이고 런던파는 런던의 한 프로그래밍 커뮤니티에서 시작되었다.
1. 단위 테스트의 정의
•
단위 테스트에는 수많은 정의 중, 공통적인 속성을 추려내면 다음과 같다.
◦
작은 코드 조각을 검증하고
◦
빠르게 수행하며
◦
격리된 방식으로 처리하는 자동화된 테스트다.
•
고전파와 런던파가 나뉘는 부분은 마지막 격리된 방식에 대한 주제다.
1. 격리 문제에 대한 런던파의 접근
•
런던파에서는 하나의 클래스가 다른 클래스 또는 여러 클래스에서 의존하는 모든 의존성을 테스트 더블로 대체해야 한다고 한다.
•
즉 단위 테스트는 테스트 대상 클래스만 테스트하는 것이 격리된 방식이라고 생각한다.
•
이 방법의 이점은 다음과 같다.
◦
테스트가 실패하면 코드의 어느 부분이 고장 났는지 확실히 알 수 있다.
◦
같은 문제에 책임을 가지고 있는 객체 그래프를 분할할 수 있다.
2. 격리 문제에 대한 고전파의 접근
•
고전파에서는 단위 테스트의 테스트끼리 독립적으로 동작하는 것을 격리된 방식이라고 생각한다.
•
이 방법의 이점은 다음과 같다.
◦
테스트가 서로 소통하고 실행 컨텍스트에 영향을 줄 수 있다.
◦
데이터베이스, 파일 시스템 등 프로세스의 외부 의존성이 이러한 공유 상태의 대표적인 예다.
3. 공유 의존성, 비공개 의존성, 프로세스 외부 의존성, 휘발성 의존성
•
공유 의존성은 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성이다.
◦
정적 가변 필드(static mutable field)가 대표적인 예로 이런 필드의 변경 사항은 동일한 프로세스에서 실행되는 모든 단위 테스트에서 가시적이다.
◦
데이터베이스도 이런 공유 의존성의 전형적인 예중 하나다.
•
휘발성 의존성은 다음 속성 중 하나를 나타내는 의존성이다.
◦
개발자의 기기에 설치된 환경 외에 런타임 환경의 설정 및 구성을 요구한다. 데이터베이스와 API 등을 예로 들 수 있다. 추가 설정이 필요하며 시스템에 기본으로 설치되어있지 않다.
◦
각 호출 시점에 따라 다른 결과를 제공하는 비결정적 동작을 포함한다.
▪
난수 생성기나 현재 날짜와 시간을 반환하는 클래스들이 해당한다.
◦
공유 의존성과 휘발성 의존성은 겹치는 부분이 있다.
▪
데이터베이스는 공유 의존성이면서 휘발성 의존성이다. 파일 시스템은 휘발성이 아니다.
▪
난수 생성기는 휘발성이지만 각 단위 테스트마다 별도의 인스턴스를 생성할 수 있으므로 공유 의존성이 아니다.
•
비공개 의존성은 공유하지 않는 의존성이다.
•
프로세스 외부 의존성은 애플리케이션 실행 프로세스 외부에서 실행되는 의존성이다.
◦
예를 들어 데이터베이스는 프로세스 외부이면서 공유 의존성이다.
◦
만약 각 테스트 실행 전 도커 컨테이너로 데이터베이스를 시작하면 테스트가 더 이상 동일한 데이터베이스 인스턴스로 동작하지 않기 때문에 프로세스 외부면서 비공개 의존성이다.
2. 단위 테스트의 런던파와 고전파
•
런던파는 테스트 대상 시스템에서 협력자 객체를 격리하는 것으로 본다.
•
고전파는 단위 테스트끼리 격리시키는 것으로 본다.
•
이를 조금 더 세분화해보자면 다음과 같은 세 가지 주요 주제에 대해 의견 차이가 존재한다.
◦
격리 주체
◦
단위의 크기
◦
테스트 대역 사용 대상
격리 주체 | 단위의 크기 | 테스트 대역 사용 대상 | |
런던파 | 단위 | 단일 클래스 | 불변 의존성 외 모든 의존성 |
고전파 | 단위 테스트 | 단일 클래스 또는 클래스 세트 | 공유 의존성 |
1. 고전파와 런던파가 의존성을 다루는 방법
•
런던파에서는 불변 의존성인 Enum 객체 Product를 제외한 나머지를 목으로 바꿔서 사용한다.
◦
이러한 불변 객체를 Value Object라고 하며 주요 특징은 내용에 의해서만 식별된다는 것이다.
◦
예를 들어 데이터베이스는 공유 의존성이며 내부의 상태는 모든 자동화된 테스트에서 공유한다.
협력자 vs 의존성
도메인 객체와 외부 의존성을 생각해보면 비유할 수 있을 듯 하다.
3. 고전파와 런던파의 비교
•
고전파와 런던파의 주요한 차이는 단위 테스트의 정의 중 세 번째인 격리 문제를 어떻게 다루는가에 있다.
◦
즉 테스트할 단위의 처리와 의존성 취급에 대한 방법으로 넘어간다.
•
저자는 고전파를 더 선호한다 그 이유는 고품질의 테스트를 만들고 단위 테스트의 목표인 생산성을 유지하는 데 더 적합하기 때문이다.
◦
그 이유는 취약성에 있는데, 목을 사용한 테스트는 고전파의 테스트보다 더 불안정한 경향이 있기 때문이다.
•
먼저 런던파의 장점을 하나씩 살펴보자.
◦
입자성이 좋다. 한 번에 한 클래스만 확인한다.
◦
클래스의 그래프가 커져도 테스트하기 쉽다. 모든 협력자는 테스트 더블로 대체되기 때문이다.
◦
테스트가 실패했다면 어떤 기능이 실패했는지 확연하게 알 수 있다.
1. 한 번에 한 클래스만 테스트 하기
•
테스트는 비즈니스 로직을 검증해야 한다. 즉 문제 해결 영역에 의미가 있는 부분을 검증해야 한다.
◦
동작 단위를 구현하는데 클래스가 얼마나 필요한지는 상관없다. 단위는 여러 클래스에 걸쳐 있거나 한 클래스에 있을수도 더욱이 아주 작은 메소드일 수도 있다.
•
입자성이 좋은 테스트를 목표로 하는 것은 도움이 되지 않는다.
2. 상호 연결된 클래스의 큰 그래프를 단위 테스트하기
•
실제로 생성된 협력자를 대신해 목을 사용하면 클래스를 쉽게 테스트할 수 있다.
◦
고전파를 사용한다면 테스트 객체를 위한 의존성을 준비해주어야 하므로 비용이 많이 들 수 있다.
•
모두 사실이지만 이것은 잘못된 문제에 초점을 맞추고 있는 것이다.
◦
테스트를 위해 어떻게 연결할 것인가 아닌 어떻게 연결을 단순화할 것인가에 대해서 생각하여야 한다.
3. 버그 위치 정확히 찾아내기
•
고전파 스타일의 단위 테스트를 채택한다면 하나의 클래스가 문제가 될 때 해당 클래스를 사용하는 모든 클래스에 문제가 생길 것이다.
◦
이는 문제의 원인을 찾는 것이 어려워진다. 그러나 이는 우려할만은 하지만 큰 문제는 아니다.
4. 고전파와 런던파 사이의 다른 차이점
•
고전파와 런던파 사이에 남아있는 두 가지 차이점은 다음과 같다.
◦
테스트 주도 개발을 통한 시스템 설계 방식
◦
과도한 명세 문제
•
테스트 주도 개발을 통한 시스템 설계 방식
◦
런던파의 단위테스트는 하향식 TDD로 이어지며 상위 계층부터 테스트를 진행해 내려가는 식이다.
▪
이때 목을 사용해서 하위 계층을 대신한다. 테스트 시 SUT의 모든 협력자를 차단해 해당 협력자의 구현을 나중으로 미룰 수 있다.
◦
고전파는 테스트에서 실제 객체를 다뤄야하기 때문에 되려 상향식으로 한다.
▪
고전적 스타일에서는 도메인 모델부터 시작하여 클라이언트가 요구하는 값을 처리하기까지 계층을 추가한다.
•
과도한 명세 문제
◦
두 스타일의 가장 큰 차이점으로 테스트 코드가 SUT의 구현 세부 사항에 결합되는 것이다.
◦
런던파는 테스트가 더 자주 결합되는 편이다.
4. 두 분파의 통합 테스트
•
런던파와 고전파는 통합 테스트의 정의에도 차이가 있다.
•
런던파는 실제 의존성 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다. 즉 고전파들이 작성한 단위 테스트를 런던파들은 통합 테스트로 여길 수도 있다는 것이다.
단위 테스트의 세 가지 속성
•
런던파는 다음과 같다.
◦
작은 코드 조각을 검증하고 빠르게 수행하며 격리된 방식으로 처리한다
•
고전파는 다음과 같이 정의한다.
◦
단일 동작 단위를 검증하고 빠르게 수행하며 다른 테스트와 별도로 처리한다
•
통합 테스트는 고전파 관점에서 단위 테스트의 속성을 위반하는 테스트다.
◦
공유 의존성에 접근하는 테스트는 다른 테스트와 별도로 처리할 수 없다.
◦
프로세스 외부 의존성에 접근하면 테스트를 빠르게 수행할 수 없다.
◦
둘 이상의 동작 단위를 검증한다면 이는 통합 테스트다.
1. 통합 테스트의 일부인 E2E 테스트
•
통합 테스트는 공유 의존성, 프로세스 외부 의존성뿐 아니라 다른 팀이 개발한 코드 등과 통합해 작동하는지도 검증하는 테스트다.
◦
E2E 테스트는 통합 테스트의 일부다. E2E 테스트도 코드가 프로세스 외부 종속성과 함께 어떻게 작동하는지 검증한다.
▪
둘의 차이점은 E2E가 일반적으로 의존성을 더 많이 포함한다는 것이다.
•
일반적으로 통합 테스트는 프로세스 외부 의존성을 한 두개만 가지고 작동한다.
◦
반면 E2E는 프로세스 외부 의존성을 대다수, 전부 가지고 작동한다. 따라서 E2E는 모든 외부 애플리케이션을 포함해 시스템을 클라이언트의 관점에서 검증하는 것을 의미한다.
•
UI 테스트, GUI 테스트, 기능 테스트와 같은 용어도 사용하지만 모두 동의어다.
•
애플리케이션이 데이터베이스, 파일 시스템, 결제 게이트웨이라는 세 가지 프로세스 외부 의존성으로 작동한다고 가정하자.
◦
일반적인 통합 테스트는 데이터베이스와 파일 시스템만 포함하고 결제 게이트웨이는 테스트 더블로 대체한다.
◦
데이터베이스와 파일 시스템은 완전히 제어할 수 있기 때문이다. 즉 프로세스 외부 의존성이 만약 외부 API라면 제어할 수 없으므로 의존하지말고 테스트 더블을 사용한다.
•
E2E 테스트는 유지 보수 측면에서 비용이 가장 많이 들기 때문에 모든 단위 테스트와 통합 테스트를 통과한 후 실행하는 것이 좋다.
요약
•
이 장에서는 단위 테스트의 정의를 다듬었다.
◦
단일 동작 단위를 검증하고
◦
빠르게 수행하며
◦
다른 테스트와 별도로 처리한다.
•
격리 문제를 주로 논의하였는데, 이 논쟁으로 고전파, 런던파가 나뉜다. 이런 의견 차이는 단위가 무엇인가에 대한 관점과 테스트 대상 시스템의 의존성 처리 방식에 영향을 미친다.
◦
런던파는 테스트 대상 단위를 서로 분리해야 한다고 한다. 테스트 대상 단위는 코드 블록 또는 보통 단위 클래스다. 불변 의존성을 제외한 모든 의존성을 테스트 더블로 처리해야 한다.
◦
고전파는 단위가 아니라 단위 테스트를 서로 분리해야 한다고 한다. 또한 테스트 대상 단위는 코드 단위가 아니라 동작 단위다. 따라서 공유 의존성만 테스트 더블로 대체해야 한다.
•
런던파는 더 나은 입자성, 상호 연결된 클래스의 큰 그래프가 제공하는 의존성 주입의 골치아픔 그리고 테스트 실패 후 버그가 있는 기능을 쉽게 찾을 수 있는 편의성을 제공한다.
◦
이는 처음에 매력적으로 보이나 몇 가지 문제가 있다.
▪
테스트 대상 클래스에 대한 관점이 잘못되었다. 코드 단위가 아닌 동작 단위를 테스트해야 한다.
▪
코드 조각을 단위 테스트하기 힘들다는 것은 설계에 문제가 있다는 반증이다. 또한 테스트 더블을 사용한다고 해도 문제를 해결하는 것이 아니라 회피하는 것이다.
▪
테스트 실패 후 어떤 기능에 버그가 있는지 판단하는 것이 도움은 되지만 종종 버그의 원인을 알고 있기 때문에 큰 장점은 아니다.
◦
런던파의 가장 큰 문제는 과잉 면세, 즉 SUT 세부 구현에 결합된 테스트 문제이다.
•
통합 테스트는 단위 테스트 기준 중 하나 이상을 위반하는 테스트다.
•
E2E 테스트는 통합 테스트의 일부다. 클라이언트의 관점에서 시스템을 검증한다. E2E 테스트는 애플리케이션과 함께 작동하는 프로세스 외부 의존성의 전부 또는 대부분에 접근한다.
•
저자의 책추천
◦
고전파를 따르는 명저로 켄트 벡의 TDD를 추천한다고 한다.
◦
런던파에 대한 자세한 내용은 스티브 프리먼과 냇 프라이스의 Growing Object-Oriented Software, Guided By Tests
◦
의존성 작업에 대해 자세하게 알고 싶다면 스티븐 반 듀르센과 마크 시먼의 Dependency Injection: Principles, Pratices, Patterns