대한 정의 – 단위 테스트를 바라보는 두 가지 견해: 고전파와 런던파 – 두 분파의 통합테스트에 대한 관점까지 • 자주 나오는 용어 – 테스트 대상 시스템(SUT, System under test) → 테스트 대상의 클래스 전체 – 테스트 대상 메소드(MUT, Method under test) → 테스트에서 호출한 SUT의 메소드 – 의존성 – 테스트가 엮이는 코드나 시스템을 의미 – 목(mock) – 테스트 대역(test double)중 하나; 5장에서 다시 봅니다.
속성을 강조 – 작은 코드 조각(단위)을 검증 – 빠르게 수행할 수 있음 (?) – 격리된 방식으로 처리하는 자동화된 테스트 (?????????) • 빠르다는 것은? – 테스트 스위트를 빠르게 구동하면 됨. 큰 이견은 없음 • 격리된 방식으로 처리하는 자동화된 테스트 – ‘격리’에 대한 관점으로 두 분파가 생김 • 고전파(The Classical Schools)[1] • 런던파(The London Schools)[2] [1]: 고전파의 명저: Kent Back의 Test-driven Development: By Example [2]: 런던파의 명저: Steve Freeman과 Nat Pryce의 Growing Object-oriented Software, Guided by Tests
격리 – 하나의 클래스가 여러 클래스에 의존하면? → 그 의존성을 모두 테스트 대역(test double)로 교체 – 동작과 외부 영향을 분리 • Pros? 1. 테스트가 실패하면 코드베이스의 문제파악이 쉽다 2. 객체 그래프(object graph) 분할이 가능 → 객체 간 참조에 대한 복잡한 내용을 테스트 대역으로 교체 [1]: 공유하거나 변경 가능한 의존성. 후에 자세히 설명합니다.
검증해야 하는가? – 가능하면 단일 클래스 혹은 해당 클래스 내의 메소드 만을 검증하도록 • 이를 위해 단위 테스트를 격리하여 실행 1. 병렬/순차 실행 가능 2. 서로의 결과에 영향을 끼치지 않음 • 단위 테스트 격리 – 보다 자세히… – 여러 테스트를 한 번에 테스트 할 수 있음 (조건: 여러 클래스가 모두 메모리가 상주하고 공유상태[1]에 도달하지 않는 경우) • E.g., 어느 테스트가 Arrange 단계에서 DB 고객 생성 다른 테스트에서 고객 삭제. 병렬 실행 시 둘 중 하나는 실패 (∵ 다른 테스트의 간섭 때문) [1]: 데이터베이스, 파일 시스템 등의 프로세스 외부 의존성. 상세 설명은 이후 이어집니다.
1. 테스트 간에 공유되고 서로의 결과에 영향을 미칠 수 있는 수단을 제공하는 의존성 2. 동일 프로세스 내에서 실행되는 모든 단위 테스트 3. 예시: 데이터베이스, static mutable field • 비공개 의존성(Private dependency) 1. 공유되지 않는 의존성 • 프로세스 외부 의존성(Out-of-process dependency) 1. 애플리케이션 실행 프로세스 외부에서 실행되는 의존성. 아직 메모리에 없는 프록시 2. 대부분은 공유 의존성이지만, 반드시 그렇지는 않음 3. 예시: 데이터베이스(공유 의존성이자 프로세스 외부 의존성) 1. 테스트 실행 전 도커 컨테이너로 DB를 사용하면 동일한 인스턴스로 사용하지 않음 → 결과 의존성 분리
– 아래 두 속성 중 하나를 나타내는 의존성 • 개발자 머신에 설치된 기본설정 이 외의 런타임 환경설정(시스템에 기본적으로 없음) • 비결정적 동작(non-deterministic behavior)을 포함 – E.g., 난수 생성기, 현재 날짜/시각 반환 – 공유 의존성과 휘발성 의존성은 겹치는 부분이 있다 • 예를 들어봅시다 – DB 의존성 (공유 의존성, 휘발성 의존성) – 파일 시스템 • 휘발성 (X) – 모든 개발자 머신에 설치됨. 결정적으로 작동됨 • 공유 의존성 (O) – 단위 테스트가 실행 컨텍스트를 서로 방해할 수 있는 수단이 될 수 있음 – 난수 생성기 • 휘발성 (O) – 비결정적 동작을 포함 • 공유 의존성 (X) – 각 테스트마다 별도의 인스턴스를 전달할 수 있음
Product는 값 객체(VO, Value Object)[1] 로서 그대로 사용 – DB → 공유의존성 내부 상태(테스트 더블로 대체되지 않은 값)는 모든 테스트에서 공유함 – Store 인스턴스 → 변경 가능한 비공개 의존성 – Product 인스턴스 → 불변인 비공개 의존성 (VO의 예시) [1]: 해당 링크를 일독바랍니다. (비공식 한국어 번역은 여기)
– 협력자(collaborator): 공유 가능하거나 변경 가능한 의존성 • E.g., DB: 공유 의존성이므로 DB 접근권한을 제공하는 클래스는 협력자다. • Store: 시간에 따라 상태가 변할 수 있으므로 협력자다 • Product, 숫자 5: 의존성, 협력자는 아님. (값 혹은 VO) • 의존성에 대해 – 공유이지만 프로세스 외부가 아닌 의존성 • 싱글턴 – 공유이자 프로세스 외부에 있음 • 데이터베이스 – 프로세스 외부에 있지만 공유되지 않음 • 읽기 전용 API 서비스 (수정불가, 실행흐름에 영향 없음) [1]: 해당 링크를 일독바랍니다. (비공식 한국어 번역은 여기)
– 협력자(collaborator): 공유 가능하거나 변경 가능한 의존성 • E.g., DB: 공유 의존성이므로 DB 접근권한을 제공하는 클래스는 협력자다. • Store: 시간에 따라 상태가 변할 수 있으므로 협력자다 • Product, 숫자 5: 의존성, 협력자는 아님. (값 혹은 VO) • 공유 의존성 對 프로세스 외부 의존성 – 다른 테스트에 영향을 주는가? – 빠른 결과, 안정적인 결과를 반환하는가? [1]: 해당 링크를 일독바랍니다. (비공식 한국어 번역은 여기)
- 격리 문제를 어떻게 다루느냐 – 테스트 단위와 의존성을 어떻게 다루는지에 대한 차이 – 저자는 고전파를 선호. 목 사용 테스트의 불안정함을 경계함. • 런던파의 이점을 아래 세 가지로 정의 – 입자성(granularity)이 좋다; 세밀한(fine-grained) 테스트 – 한 번에 한 클래스만 관리 – 서로 연결된 클래스의 그래프가 커져도 테스트가 쉬움 – 테스트 대역 사용으로 인함 – 테스트 실패 시 추적이 쉬움
위치 정확히 찾기 – 런던 스타일 테스트는 SUT의 버그가 포함된 테스트가 실패함 – 고전파 스타일 테스트는 클라이언트에서도 버그를 찾을 수 있음… • …테스트를 통해 디버깅이 어려워지는 듯 하지만 • 자주 테스트하면 버그의 원인을 쉽게 찾을 수 있고 • 한 코드의 변화가 여러 테스트에 영향이 생기는 것은 전체 시스템이 그 코드에 의존한다는 것을 역추적할 수 있음 → 리팩토링 대상
런던파 사이의 다른 차이점 (1) - TDD를 통한 시스템 설계 방식 – 런던 스타일: 하향식 TDD • 상위 레벨 테스트부터. 시스템이 통신해야 할 협력자를 지정하고 이를 목으로 처리 – 고전 스타일: 상향식 TDD • 도메인 모델부터 최종 사용자의 소프트웨어 사용까지
접근하는 테스트는 분리실행이 불가 1. 테스트 DB에 쓰기 연산을 하면 다른 값도 자동으로 영향 받음 간섭을 피하기 위한 조치가 필요, 순차실행 필요 2. 프로세스 외부 의존성에 접근하면 테스트가 느려짐 1. 외부 API나 DB에 콜하는 행동 자체가 시간적으로 느림 3. 둘 이상의 동작단위를 검증하면 통합테스트 1. 둘 이상의 테스트를 하나로 합치거나 여러 모듈을 동시에 테스트? 통상적으로 프로세스 외부 의존성을 필요로 함
테스트? 1. 공유 의존성, 프로세스 외부 의존성 뿐 아니라 조직 내 다른 팀이 개발한 코드 등과 통합하여 작동을 검증하는 테스트 2. 의존성을 더 많이 포함하는 경우를 의미 → 그만큼 비용이 많이 들기 때문에 제품 나가기 전 최후반에 실행해야 함 → 이 때 외부 의존성을 쓰지 못할 수도 있음 (E.g., 다우기술 결제 콜 등) → 이럴 때 테스트 대역을 쓰면 좋음! 3. 동의어: UI 테스트, GUI 테스트, 기능 테스트, etc.