단위 테스트: 협력자와 의존성을 구분하는 법
단위 테스트를 정확히 이해하고 잘 설계하기 위해서는 테스트 대상 객체(SUT)와 그 객체가 의존하는 협력자(Collaborator), 그리고 값(Value) 간의 구분이 매우 중요하다. 이 글은 『단위 테스트: 생산성과 품질을 위한 단위테스트 원칙과 패턴』 책을 기반으로, 궁금해할 만한 것들을 중심으로 정리 했다.
협력자란 무엇인가?
협력자는 테스트 대상이 사용하는 의존성 중, 외부 시스템과 연결되어 있거나 내부 상태가 바뀌는 객체다. 예를 들어 DB를 다루는 UserRepository, 외부 API를 호출하는 MailSender, 내부 상태가 변하는 Store 등이 협력자에 해당한다. 테스트 시 이런 협력자는 mock, stub, fake 등으로 대체해야 한다. 반면, 상태가 없고 바뀌지 않는 단순 값은 협력자가 아니다.
비공개 의존성과 협력자
SUT(System Under Test) 내부에서 직접 생성되어 외부에서 주입할 수 없는 의존성을 '비공개 의존성'이라 부른다. 이 중 상태가 변하는 객체는 '변경 가능한 비공개 의존성'이며, 테스트에서 문제를 일으킬 수 있다. 런던파의 테스트 철학에 따르면, 이런 비공개 의존성도 리팩터링을 통해 외부 주입 가능하게 만들고 테스트 대역으로 교체해야 한다.
공유 의존성과 테스트 격리
여러 테스트가 같은 인스턴스를 공유하는 경우, 이를 '공유 의존성'이라 한다. 대표적으로 싱글턴 객체, 정적 객체, 데이터베이스 등이 있다. 이런 의존성은 테스트 간 간섭을 유발하므로, 격리하거나 테스트 대역으로 바꾸는 것이 권장된다.
런던파는 어떻게 보는가?
런던파(London School)는 테스트 대상 외의 모든 협력자는 mock 등으로 격리해야 한다고 주장한다. 단, 불변 의존성 — 상태가 없고 결과가 항상 일정한 순수 함수나 계산 로직 — 은 예외로 본다. 테스트가 어렵다면, 그것은 설계가 잘못된 것이라는 입장을 갖고 있다.
런던파와 고전파 비교
| 구분 | 런던파 (London School) | 고전파 (Classic School) |
|---|---|---|
| 테스트 초점 | 객체 간 상호작용 검증 | 상태 변화와 최종 결과 검증 |
| 협력자 처리 방식 | mock, stub 등으로 격리 | 실제 객체를 최대한 사용 |
| 설계 영향 | 테스트 가능성을 기준으로 설계 유도 | 실제 동작 위주로 설계 유지 |
| 테스트 대역 사용 | 적극적으로 사용 (mock 기반) | 최소한으로 사용 |
| 실패 원인 파악 | 상호작용 실패 여부 확인 | 상태 불일치로 인한 실패 분석 |
| 대표 철학 | 테스트는 설계 유도 도구 | 테스트는 기능 검증 도구 |
주요 용어 정의
의존성 (Dependency)
한 객체가 다른 객체의 기능이나 데이터에 의존할 때 그 관계를 의존성이라 한다. 클래스 간의 결합도를 나타내며, 설계나 테스트 전략에서 중요한 기준이 된다.
협력자 (Collaborator)
테스트 대상 객체(SUT)가 상호작용하는 의존성 중, 외부 시스템과 연결되어 있거나 상태가 변할 수 있는 객체를 말한다. Repository, API 클라이언트, 상태가 있는 서비스 등이 여기에 해당하며, 테스트 시 mock 등으로 대체한다.
값 (Value)
상태가 없고 불변이며 단순히 전달되는 데이터 객체이다. 숫자, 문자열, enum, 불변 객체 등이 여기에 해당하며, 테스트 대상과의 상호작용 없이 사용된다.
값 객체 (Value Object)
도메인의 특정 개념을 표현하는 불변 객체로, 값 자체에 의미가 있다. 예: Money, ProductCode, DateRange 등. 상태는 존재하지만 외부 상호작용이나 변경은 발생하지 않으므로 협력자가 아니다.
비공개 의존성 (Private Dependency)
테스트 대상 객체 내부에서 직접 생성되어 외부에서 주입하거나 교체할 수 없는 의존성을 의미한다. 테스트에서 통제가 어려워지고 재사용이나 대역 교체가 불가능하다.
변경 가능한 비공개 의존성
상태가 변하는(mutable) 객체이면서 외부에서 접근할 수 없는 비공개 의존성이다. 테스트 간 상태 공유나 부작용을 유발할 수 있어 테스트 가능성을 해친다.
공유 의존성 (Shared Dependency)
여러 테스트나 인스턴스가 동시에 사용하는 의존성을 말한다. 대표적으로 싱글턴, 정적 객체, DB 연결 등이 있으며, 테스트 격리나 대역 교체가 필요하다.
불변 의존성 (Immutable Dependency)
상태가 없고 외부 환경에 영향 받지 않으며 항상 동일한 결과를 반환하는 객체이다. 단순 계산 클래스나 순수 함수형 객체가 여기에 해당하며, mock 대체가 필요 없다.
테스트 대역 (Test Double)
협력자 객체를 테스트 용도로 대체한 가짜 객체이다. mock, stub, fake, spy, dummy 등 다양한 종류가 있으며, 테스트 격리성과 예측 가능성을 높인다.
SUT (System Under Test)
현재 테스트 대상인 시스템 또는 객체를 의미한다. 단위 테스트에서는 보통 하나의 SUT만 존재하며, 협력자와 구분되어야 한다.
격리 (Isolation)
테스트 대상이 외부 시스템이나 협력자에 영향을 받지 않도록 환경을 제어하는 것을 말한다. mock 등 테스트 대역을 통해 테스트가 빠르고 안정적으로 수행되도록 만든다.
런던파 (London School)
단위 테스트 철학 중 하나로, 테스트 대상 외의 모든 협력자는 mock 등으로 격리해야 한다고 본다. 테스트는 객체 간 상호작용 검증에 초점을 맞추며, 설계 유도 도구로 삼는다.
고전파 (Classic School)
테스트를 상태 기반으로 검증하며, 실제 구현을 사용하는 테스트 방식을 선호한다. mock 사용은 최소화하고, 기능 결과 자체를 검증하는 데 집중한다.