책 '단위 테스트'를 읽고 정리한 내용입니다.
2장 단위 테스트란 무엇인가
2.1 '단위 테스트'의 정의
- 단위 테스트의 세 가지 속성
- 작은 코드 조각(단위라고도 함)을 검증하고,
- 빠르게 수행하고,
- 격리된 방식으로 처리하는 자동화된 테스트다.
- 세 가지 속성 중 '격리'가 무엇인지에 대한 의견 차이로 고전파와 런던파가 시작되었다.2.1.1 격리 문제에 대한 런던파의 접근
- 런던파에서의 '격리': 테스트 대상 시스템을 협력자에게서 격리하는 것
- 의존성을 테스트 대역으로 대체해서 테스트 대상 클래스에만 집중할 수 있다.
- 장점
- 테스트가 실패하면 어느 부분이 문제인지 확실히 알 수 있다. (= 테스트 대상에 문제가 있는 것을 바로 알 수 있다.)
- 객체 그래프(같은 문제를 해결하는 클래스의 통신망)를 분할할 수 있다. -> 특정 문제에 대한 의존성을 분리해서 볼 수 있다는 의미인듯
- 테스트 대상의 의존성, 그 의존성의 의존성을 다룰 필요가 없어 객체 그래프를 다시 만들지 않아도 된다.
- 한 번에 한 클래스만 테스트하라는 지침을 도입하면 전체 단위 테스트 스위트의 구조가 간단해진다.
- 목 프레임워크: C# Moq, NSubstitute / JAVA Mockito, JMock, EasyMock
- 테스트 대역은 실행과 관련 없이 모든 종류의 가짜 의존성을 설명하는 포괄적인 용어이며, 목은 그러한 의존성의 한 종류일 뿐이다.2.1.2 격리 문제에 대한 고전파의 접근
- 코드를 꼭 격리하는 방식일 필요는 없지만, 단위 테스트는 서로 격리해서 실행해야 한다.
=> 여러 단위 테스트가 실행 순서에 상관 없이 항상 성공해야 한다.
- 의존성의 종류
- 공유(shared) 의존성: 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성 (예: static mutable field, DB, 파일 시스템)
- 비공개(private) 의존성: 공유하지 않는 의존성
- 프로세스 외부(out-of-process) 의존성: 애플리케이션 실행 프로세스 외부에서 실행되는 의존성
- 휘발성 의존성
- 개발자 머신에 기본 설치된 환경 외에 런타임 환경의 설정 및 구성을 요구한다. 예: 데이터베이스, API 서비스
- 비결정적 동작을 포함한다. 예: 난수 생성기, 현재 날짜와 시간을 반환하는 클래스
- 테스트 간에 공유 상태를 일으키는 공유 의존성에 대해서는 테스트 간에 공유되거나 테스트 대역으로 대체되어야 한다.
- 테스트마다 공유 의존성을 만들 수 없기 때문
- 공유 의존성은 실행 프로세스 외부에 있는 경우가 많아서 호출이 오래 걸리기 때문 => 빠르게 수행되어야 한다는 전제에 어긋남
- 고전파는 단위가 반드시 클래스가 국한될 필요가 없다고 본다. => 공유 의존성이 없는 여러 클래스가 단위가 될 수 있다고 생각한다.2.2 단위 테스트의 런던파와 고전파
- 격리 특성에 대한 각 분파의 생각
- 런던파: 테스트 대상 시스템에서 협력자를 격리하는 것
- 고전파: 단위 테스트끼리 격리하는 것
|
격리 주체 |
단위의 크기 |
테스트 대역 사용 대상 |
런던파 |
단위 |
단일 클래스 |
불변 의존성 외 모든 의존성 |
고전파 |
단위 테스트 |
단일 클래스 또는 클래스 세트 |
공유 의존성 |
2.2.1 고전파와 런던파가 의존성을 다루는 방법
|
공유 의존성 |
변경 가능한 비공개 의존성 |
불변인 의존성 |
런던파 |
교체 대상 |
교체 대상 |
교체 대상 아님 |
고전파 |
교체 대상 |
교체 대상 아님 |
교체 대상 아님 |
- 협력자와 값 객체
- 협력자: 공유하거나 변경 가능한 의존성
- 값 객체: 불변 객체
- 공유 의존성과 프로세스 외부 의존성
- 공유 의존성이지만 프로세스 외부 의존성이 아닌 예: 싱글턴, 클래스의 정적 필드
- 공유 의존성이면서 프로세스 외부 의존성인 예: 데이터베이스 (<-변경 가능)
- 공유 의존성이 아니지만 프로세스 외부 의존성인 예: 읽기 전용 API 서비스 (<-불변)
- 실제 프로젝트에서 프로세스 외부가 아닌 공유 의존성은 거의 없다.
2.3 고전파와 런던파의 비교
- 목을 사용하는 테스트는 고전적인 테스트보다 불안정한 경향이 있다. 필자는 개인적으로 고전파를 선호한다고 한다.
- 런던파의 장점
- 입자성이 좋다. 테스트가 세밀해서 한 번에 한 클래스만 확인한다.
- 서로 연결된 클래스의 그래프가 커져도 테스트하기 쉽다. 모든 협력자는 테스트 대역으로 대체되기 때문에 테스트 작성 시 걱정할 필요가 없다.
- 테스트가 실패하면 어떤 기능이 실패했는지 확실히 알 수 있다. 클래스의 협력자가 없으면 테스트 대상 클래스 외에 다른 것을 의심할 여지가 없다.
2.3.1 한 번에 한 클래스만 테스트하기
- 런던파는 클래스 단위로 단위 테스트를 한다.
- 하지만 테스트는 코드의 단위가 아닌 동작의 단위를 검증해야 한다. 즉, 단위는 여러 클래스에 걸쳐 있거나 한 클래스에만 있거나 아주 작은 메서드가 될 수도 있다.
2.3.2 상호 연결된 클래스의 큰 그래프를 단위 테스트하기
- 의존성 그래프가 복잡할 때 목을 사용하면 테스트가 쉽다. (런던파)
- 하지만, 먼저 복잡한 그래프를 갖지 않는 것에 집중해야 한다. 클래스 그래프가 커진 것은 코드 설계 문제이다.
2.3.3 버그 위치 정확히 찾아내기
- 런던 스타일 테스트를 사용하면 SUT에 버그가 포함된 테스트만 실패하지만, 고전적인 방식이면 전체 시스템에 걸쳐 테스트 실패를 야기할 수 있다.
- 고전적인 방식의 이러한 문제점을 해결하기 위해서는 테스트를 정기적으로(이상적으로는 소스 코드가 변경될 때마다) 실행하면 버그의 원인을 알아낼 수 있다.
- 테스트 스위트 전체에 걸쳐 실패하는 것도 가치가 있다. 그만큼 중요한 부분에 버그가 있다는 뜻이기 때문.
2.3.4 고전파와 런던파 사이의 다른 차이점
- 테스트 주도 개발을 통한 시스템 설계 방식
- 테스트 주도 개발의 3단계
- 추가해야 할 기능과 어떻게 동작해야 하는지를 나타내는 실패 테스트를 작성한다.
- 테스트가 통과할 만큼 충분히 코드를 작성한다. 이 단계에서 코드가 깨끗하거나 명쾌할 필요는 없다.
- 코드를 리팩터링한다. 통과 테스트 보호하에서 코드를 안전하게 정리해 좀 더 읽기 쉽고 유지하기 쉽도록 만들 수 있다.
- 런던파: 하향식 TDD. 전체 시스템에 대한 기대치를 설정하는 상위 레벨 테스트부터 시작한다.
- 고전파: 상향식 TDD. 도메인 모델부터 시작한다.
- 과도한 명세 문제
- 런던파가 고전파보다 테스트가 구현에 자주 결합되는 편이다.
2.4 두 분파의 통합 테스트
- 런던파의 통합 테스트: 실제 협력자 객체를 사용하는 모든 테스트를 통합 테스트로 간주한다.
- 고전파의 통합 테스트
- 단위 테스트의 3가지 속성 (단일 동작 단위 검증, 빠르게 수행, 다른 테스트와 별도로 처리) 중 하나 이상을 충족하지 않는 테스트
- '다른 테스트와 별도로 처리' 미충족: 공유 의존성에 접근하는 테스트
- '빠르게 수행' 미충족: 프로세스 외부 의존성에 접근하는 테스트
- '단일 동작 단위 검증' 미충족: 둘 이상의 동작 단위를 검증하는 테스트, 다른 팀이 개발한 모듈이 둘 이상 있을 때 한번에 검증하는 테스트
- 통합 테스트란? 공유 의존성, 프로세스 외부 의존성뿐 아니라 조직 내 다른 팀이 개발한 코드 등과 통합해 작동하는지도 검증하는 테스트!
2.4.1 통합 테스트의 일부인 엔드 투 엔드 테스트
- 엔드 투 엔드 테스트
- 통합 테스트의 일부. 프로세스 외부 종속성과 함께 어떻게 작동하는지 검증한다.
- 일반적인 통합 테스트보다 의존성을 더 많이 포함한다.
- 동의어: UI 테스트, GUI 테스트, 기능 테스트
요약
- 단위 테스트의 정의
- 단일 동작 단위를 검증하고
- 빠르게 수행하고
- 다른 테스트와 별도로 처리한다.
- 격리 논쟁으로 고전파와 런던파 두 개의 단위 테스트 분파가 나뉘었다.
- 런던파의 격리: 테스트 대상 단위를 분리해야 한다. 불변 의존성을 제외한 모든 의존성을 테스트 대역으로 대체해야 한다.
- 고전파의 격리: 단위가 아니라 단위 테스트를 서로 분리해야 한다. 테스트 대상 단위는 코드 단위가 아니라 동작 단위이다. 따라서 공유 의존성만 테스트 대역으로 대체해야 한다.
- 런던파의 장점
- 더 나은 입자성
- 상호 연결된 클래스의 큰 그래프에 대한 테스트 용이성
- 테스트 실패 후 버그가 있는 기능을 쉽게 찾을 수 있는 편의성
- 런던파의 문제점
- 테스트 대상 클래스에 대한 초점이 잘못됐다. 테스트는 코드 단위가 아니라 동작 단위를 검증해야 한다.
- 코드 조각을 단위 테스트 할 수 없다는 것은 코드 설계에 문제가 있는 것이고, 테스트 대역 사용은 이 문제를 오히려 숨긴다.
- 테스트가 실패해도 버그의 원인을 찾는 것이 그리 어렵지 않다. 마지막에 수정한 것이 버그의 원인이기 때문이다.
- 런던파의 가장 큰 문제점은 과잉 명세, 즉 SUT 세부 구현에 결합된 테스트 문제다.
- 통합 테스트: 단위 테스트 기준 중 하나 이상을 충족하지 못하는 테스트
- 엔드 투 엔드 테스트: 통합 테스트의 일부. 최종 사용자의 관점에서 시스템을 검증한다. 프로세스 외부 의존성의 전부 또는 대부분에 직접 접근한다.