배경설명
단위테스트 책에서 Mock 사용을 대체로 권장하지 않지만 권장하는 경우가 있는데 외부에 하위 호환성을 지켜줄 필요가 있을 때입니다.
단위 테스트 - YES24
소프트웨어 개발에 있어 단위 테스트는 이제 선택이 아니라 필수가 됐다. 단위 테스트에 대한 오해를 바로잡고, 올바른 단위 테스트에 대한 원칙, 테스트를 작성하는 스타일과 효과적인 테스트
www.yes24.com
추가로 외부에서 하위호환성을 지켜준다면 외부 API 조회 부분을 Mock으로 대체해도 괜찮다고 생각합니다.
추가로 테스트 시에 직접 API 콜을 할 수도 있지만 그러면
1. 네트워크 장애 또는 API 서버에 문제가 발생한 경우 의도하지 않게 테스트가 깨짐.
2. 여러 부서가 함께 사용하는 Dev 환경의 경우 다른 사람의 잘못된 데이터 조작으로 인해 테스트가 깨짐.
과 같은 문제가 발생할 수 있습니다.
회사에서 테스트 작성 시에 외부 API 조회 결과를 조합하는 경우가 많았고 각 API의 응답모델이 복잡해서 프로그래밍적으로 응답 모델 객체를 직접 만들기가 굉장히 수고로운 상황이었습니다.
이와 같은 배경에서 각 API가 내려주는 응답 JSON을 따서 파일로 만들어두고 테스트 시에 API Mock 객체는 API 서버에 요청을 날리는 것이 아니라 JSON 파일을 읽어서 돌려주도록 하였습니다.
추가로 @SpringBootTest를 사용 시 @Configuration 어노테이션이 붙은 클래스를 만들고 거기에 @MockBean과 @PostConstructor를 사용하여 테스트 간 동일한 설정을 공유할 수 있도록 하였습니다.
문제점
큰 문제가 없을 것 같았는데 막상 동료들과 이야기 해보고 또 개인적으로 더 알아본 바에 의하면 다음과 같은 문제가 있습니다.
1. @SpringBootTest와 @MockBean을 함께 사용 시 테스트 간 Context Caching이 되지 않는다.
@SpringBootTest를 그래도 킅 성능 고민 없이 사용할 수 있었던 것은 테스트 시에 Bean이 재사용될 수 있도록 하는 Context Caching이 있어 여러 @SpringBootTest가 있어도 한번에 Context Loading으로 모든 테스트를 커버할 수 있기 때문입니다.
그런데 @MockBean을 사용하는 경우에는 해당 MockBean과 해당 MockBean를 사용하는 Bean들은 Caching 되지 않는 것으로 보입니다. (여러 블로그와 예전 Issue에서 확인해주고 있지만 최신 버전에서는 해당 문제가 해결 되었을 수도 있어 최신 공식 문서와 내부 동작 테스트를 통해 현재도 동일한지 확인 해. 보아야 하는 부분입니다.)
관련해서 함께 보면 좋을 글 : https://suhwan.dev/2019/03/27/spring-test-context-management-and-caching/
Spring Testing - Context Management and Caching
개요 최근 들어 회사에서 테스트 성능이 문제가 된 경우가 몇 번 있었다. 이를 해결하기 위해 Spring의 Testing 레퍼런스를 정독하며 테스트의 동작 방식에 대해서 파헤쳐보았다. 비록 내가 내린 결
suhwan.dev
이 내용대로라면 보통 API 조회는 어플리케이션의 끝부분에 위치하고 해당 클래스를 다른 클래스에서 차례대로 의존하고 있기 때문에 API를 조회하는 곳에 무분별하게 MockBean을 사용하는 것은 테스트의 성능을 생각하면 비권장되어야 하는 방법으로 보입니다.
2. intellij를 통해 실행한 것이 아니라 터미널에서 gradle로 테스트 실행 시에는 테스트가 깨진다.
아마 테스트 간 Context Cacing이 되지 않는 것과 관련이 된 것 같은데 코드 로직은 정상이고, intellij에서는 개별 테스트를 돌렸을 떄 잘 동작하는 점에서 정상 동작하여야 하는 것으로 보이는 gradlew clean test 명령어는 API MockBean을 사용하는 일부 테스트가 실패하게 만들었습니다. 정확한 원인은 추가 파악이 필요합니다.
3. 현재 연동되는 API가 10개 이상이고 각 API마다 여러 경우의 수가 있는데 점점 많아지는 JSON을 관리하기 힘들어질 것이다.
각 API가 API 응답의 하위호환성을 지켜줄 것이고 한번만 제대로 JSON을 기록해두면 된다고 생각했지만,
테스트 해야 하는 경우의 수가 많다면.. 각 테스트 경우의 수마다 JSON 파일을 만들어야 한다면.. 분명히 API 응답을 JSON으로 관리하는 것은 좋은 않은 선택입니다.
해결 방향
위와 같은 이유 때문에 다음과 같은 방향으로 해결 하는 것이 좋겠다는 결론이 났습니다.
1. 프로젝트의 구조를 테스트하기 쉽게 바꾼다.
현재 테스트가 어려운 이유는 비지니스 로직이 Service에 있고 각 Service는 API 조회 클래스를 의존하고 있기 때문입니다.
이 구조를 테스트하기 좋게 바꾸기 위해 테스트의 대상이 되는 비지니스 로직(정책)을 해당 로직만 담당하는 별도 레이어(rule or policy를 패키지명으로 사용)를 만들고 비지니스 로직은 해당 부분으로 옮기기로 했습니다.
이렇게 하면 해당 레이어(rule or policy)는 작은 부분을 테스트 하기 때문에 테스트를 위한 객체를 만들기도 쉽고, 중요 비지니스 로직이 다른 부분과 분리되어 있어 해당 부분을 관리하기도 쉬워집니다.
2. 1번으로 커버가 안되는 케이스들은 테스트가 실제 API를 사용하게 하되 로컬에서만 돌아가게 하고 향후 CI에 포함될 수 있는 방법을 마련한다.
참고로 로컬에서만 돌아가게 하는 것은 JUnit5을 사용하고 모두 Mac을 사용한다면 @EnabledOnOs(OS.MAC)를 통해 가능합니다.
이 글의 첫부분에 소개했던 실제 API를 사용했을 때의 문제점은 테스트가 배포 과정에 포함되어 의도하지 않게 배포가 불가능한 경우입니다.
로컬에서만 돌아가게 하거나 CI에서 관련 테스트에 대한 warning만 뜨게 조치할 수 있다면 Mock 보다는 실제 API를 쓰는 것이 훨씬 간편하고 안정적인 방법입니다.
3. 1, 2번으로 커버가 안되고 배포 전 반드시 확인하고 배포가 되면 좋을 것들은 각 API 별 주요 1,2개 흐름만 있을 것이므로 JSON 파일을 돌려주는 Mock을 사용한다.
경우에 따라 배포 전 테스트가 필요하고 배포 프로세스에 포함되기 때문에 각 API 조회에 대해 실제 의존성을 사용하지 않고 Mock으로 대체해야 하는 경우가 있을 수 있습니다.
3번 케이스는 1,2번에서 체크되지 않은 것들만 진행하기 때문에 경우의 수가 많지 않을 것이므로 JSON 관리의 부담도 크지 않습니다.
'제안&정리' 카테고리의 다른 글
후이즈 도메인 구매 및 AWS Certificate Manager 연결 (0) | 2022.06.05 |
---|---|
리팩토링은 점진적으로 그리고 테스트 작성부터 (0) | 2022.05.01 |
JAVA String 연결 더 잘하기 (0) | 2022.03.27 |
[테스트 코드 작성기 1] 간단한 통합 테스트 작성 (0) | 2022.03.21 |
Log 잘 남기기 (0) | 2022.01.16 |