프로젝트가 끝나고 스스로에게 가장 아쉬웠던 점은 테스트 코드였다.
- 테스트 코드 관련 의사 결정에 시간이 많이 걸렸다.
- 테스트 코드에 문제가 발생했다는 신호를 포착했음에도, 시간 부족 등의 이유로 수정을 하지 않았다. 결국 그 문제가 전체 프로젝트 진행에 발목을 잡게 되었다.
최근에 읽은 단위 테스트에서 내 프로젝트의 문제점을 분석하고 개선하는 데 도움이 되는 내용들이 있어서 정리해보았다.
책 이름과 다르게 통합 테스트에 관한 내용이다.
웹 애플리케이션에서 복잡한 알고리즘을 요구하는 도메인을 다뤄본 경험이 없는 내가 가장 많이 작성한 테스트는 통합 테스트였기 때문이다.
스프링 기준 컨트롤러와 서비스
참고 용어
외부 의존성
애플리케이션 실행 프로세스 외부에서 실행되는 의존성
관리 의존성
전체를 제어할 수 있는 프로세스 외부 의존성
애플리케이션을 통해서만 접근 가능하다.
외부 시스템은 애플리케이션의 API를 통해서만 접근할 수 있다.
데이터베이스
비관리 의존성
전체를 제어할 수 없는 프로세스 외부 의존성
해당 의존성과의 상호작용을 외부에서 볼 수 있다
SMTP 서버, 메시지 버스
다른 애플리케이션에서 볼 수 있는 부작용 발생
무거운 컨트롤러 통합 테스트
나는 프로젝트에 E2E 테스트가 반드시 있어야 한다고 생각했다.
그동안 Postman을 통해 눈으로 요청과 응답을 확인했던 것과 비교했을 때, 테스트 프레임워크를 통한 자동화된 테스트는 매력적이었다. 그래서 애플리케이션의 API를 자동화된 방식으로 검증할 수 있는 MockMvc를 활용했다.
도메인 → 서비스 → 컨트롤러 순으로 테스트 코드와 병행해서 작성했고. 얼마 지나지 않아 이상한 점들을 발견했다.
- 위의 픽스처 설정 코드가 이미 작성했던 서비스의 집들이 댓글 수정 테스트 코드와 중복되고 있었다. 이 중복을 따로 리팩터링 할 수 있으나, 여러 계층의 테스트에서 모두 fixtureProvider에 의존하는 것은 변함없기 때문에 테스트 코드가 fixtureProvider 변경에 취약해 보였다.
- 애플리케이션의 동작이 중복되어 테스트 되고 있었다.
ex) “사용자는 자신이 작성한 집들이 게시글을 수정한다.” ⇒ 컨트롤러, 서비스 둘 다 테스트
이 동작은 데이터베이스와 외부 이미지 저장소와 통신하므로, 서비스를 목 처리한 컨트롤러 테스트보다 많은 시간이 걸렸다.

서비스에서 테스트 한 코드를 다시 테스트 하는 데 1초나 더 투자할 필요가 있을까?
- 컨트롤러 테스트에서 repository를 통해 픽스처를 저장하는 로직이 있는 것이 테스트 가독성을 해친다고 느껴졌다.
이건 테스트 이름을 XXXControllerTest로 작성한 것이 원인인 것 같다. E2E 목적으로 작성한 테스트임에도 불구하고
“왜 컨트롤러 계층 테스트에서 픽스처 저장 코드를 호출해야 할까?”
라는 생각이 계속 들었다. 이렇게 작성하면 테스트 코드를 명세처럼 활용할 수 있을까?이런 문제점이 있음에도 불구하고, 시간 부족, 잘못된 지식에 대한 고집(목 처리를 최소화해야 한다는 강박 등)의 이유로 수정을 미루게 되었다.
해결책
프로젝트가 끝난 뒤 책을 읽으면서 내가 어설프게 알고 있었다는 것을 깨달았다.
대부분의 경우 통합 테스트 범주에 관리 의존성을 포함시키고 비관리 의존성만 목으로 대체하면 통합 테스트의 보호 수준이 엔드 투 엔드 테스트와 비슷해지므로 엔드 투 엔드 테스트를 생략할 수 있다. - 단위 테스트 284p
즉 통합 테스트들이 다루는 시스템이 E2E 테스트와 비슷하면 굳이 E2E 테스트를 고집할 필요 없는 것이었다. 특히 비관리 의존성이 적은 우리 프로젝트에는 더욱.
두 가지의 해결책이 생각났다.
- E2E 테스트 유지
서비스 테스트의 외부 의존성을 전부 목 처리한다.
XXXControllerTest가 아닌 E2E 테스트임을 알려주는 이름으로 변경
서비스 테스트에서는 repository 포함 외부 의존성 목 처리
- E2E 테스트 → 컨트롤러 통합 테스트
컨트롤러 테스트에서 서비스 의존성을 목 처리하고, 서비스 테스트는 일반적인 통합 테스트로 진행
1은 컨트롤러 테스트 코드가 너무 커질 것 같아서(픽스처 설정 등) 2로 정한다.
그리고 프레젠테이션 계층에서는 컨트롤러의 핵심 기능들만 테스트할 것 같다.
- 스프링 MVC를 통해 들어온 Http 요청을 컨트롤러 메서드의 매개변수로 받아온다.
- 애플리케이션 계층에 요청을 위임한다. (직접 비즈니스 로직을 책임지지 않는다. 목 처리)
- 비즈니스 로직 수행 결과를 메시지 형식에 맞춰 가공한 뒤 클라이언트에 전달한다.
E2E 테스트가 필요할 경우에는
- Controller 테스트와 구분되는 방식으로 테스트 코드를 작성하거나.
- Postman의 Scripting 기능 등을 이용해서 보다 사용자 시나리오에 가까운 형태로 테스트 해볼 것 같다.