원문 캡처 · henko.net
원문 캡처 · henko.net

테스트 대역(test double)으로 실제 의존성을 대체하면 단위 테스트 대상만 분리해 동작을 확인할 수 있다. 하지만 데이터베이스, 원격 서비스, 프레임워크, 사용자 인터페이스처럼 실제 동작이 복잡한 구성 요소를 모킹하면, 테스트가 실제 환경과 다른 가정을 검증할 위험이 있다.

실제 구성 요소를 가짜로 바꾸려면 테스트 대상 코드가 사용하는 동작을 재현해야 한다. 이 과정에서 실제 구성 요소의 동작에 대한 예상이 모형에 담기는데, 그 예상이 틀릴 수 있다. 잘못된 SQL 쿼리나 서버가 처리하지 못하는 HTTP 요청을 코드가 생성해도, 모형만으로는 문제가 드러나지 않을 수 있다. 중요한 것은 의도한 쿼리나 요청을 만들었는지가 아니라 실제로 의도한 결과를 얻는지다.

글의 원칙은 자신이 소유한 인터페이스만 모킹하는 것이다. 인터페이스를 직접 정의했다면 기대 동작도 정할 수 있어 모킹이 비교적 안전하다. 반면 다른 조직이나 라이브러리가 정의한 인터페이스는 동작을 완전히 이해하지 못했을 위험이 있다. 이런 구성 요소와 상호작용하는 코드는 몇 군데로 격리하고, 모형에 의존하는 단위 테스트보다 통합 테스트로 확인하는 편이 낫다는 주장이다.

데이터베이스와 외부 서비스 테스트

데이터베이스 테스트에서는 드라이버나 ORM(Object-Relational Mapping) 라이브러리를 모킹하지 않는 편이 좋다고 제안한다. 데이터베이스 통신을 DAO(Data Access Object) 패턴으로 감싸면, 나머지 코드가 의존할 단순한 인터페이스를 만들 수 있다. 이 DAO 인터페이스는 동작을 직접 정의했으므로 다른 단위 테스트에서 모킹하기에 더 안전하다. 다만 DAO 자체는 모형이 아니라 실제 데이터베이스를 대상으로 통합 테스트하는 편이 낫다.

DAO도 데이터베이스와 비즈니스 로직을 완전히 분리하지 못할 수 있다. 특히 ORM의 엔티티 관리자나 세션이 비즈니스 로직에 깊이 들어오면, 운영 환경에서 발생하는 오류나 동시 요청에 따른 상호작용을 예상하기 어렵다. 글쓴이는 이런 경우 실제 데이터베이스를 사용해 테스트했다고 설명한다. 그 사례에서는 테스트 시작 시 임베디드 PostgreSQL 데이터베이스를 띄우고 스키마 마이그레이션을 실행한 뒤, 테스트마다 데이터베이스를 비웠다. 이 방식은 테스트 묶음 시작에 1~2초, 테스트마다 약 50ms를 더했다고 한다. 실제 데이터베이스를 쓰는 테스트를 단위 테스트로 볼지는 정의에 따라 다를 수 있다.

테스트 머신에서 실행하기 어려운 외부 서비스에는 서버 응답을 저장하거나 기록·재생 프록시를 사용하는 방법도 있다. 실제 시스템과 한 차례 상호작용한 뒤 저장된 응답으로 테스트를 이어갈 수 있어 읽기 전용 작업에는 특히 유용하다. 하지만 상호작용이 복잡하고 쓰기 작업이 많아질수록 테스트 작성이 어려워진다. 생성되는 ID와 타임스탬프를 처리하려고 모형과 비슷한 코드를 많이 쓰게 되면, 모킹을 피하려던 목적이 퇴색할 수 있다.

테스트하기 쉬운 구조

복잡한 비즈니스 로직은 외부 구성 요소와 통신하는 코드에서 분리하면 모킹 부담 없이 단위 테스트할 수 있다. 글은 의존성이 적은 코드에 복잡성을 두고 이 부분을 단위 테스트하며, 의존성이 많고 복잡성이 낮은 코드는 통합 테스트에 집중하는 구조를 제안한다. 외부 구성 요소를 모킹하고 싶어질 때는 시스템 구조를 바꿔 모킹 자체가 필요 없도록 할 수 있는지 살펴보라는 조언이다.

관련 자료: 단위 테스트와 테스트 더블 용어, 단위 테스트가 설계를 바꾸는 방식.

원문: Only mock your own interfaces