지난번 KCRM 웹 어플리케이션이 서비스 되면서 Rest Docs 기반의 API 명세문서를 사용하게 되었고 아래와 같은 문서로 배달의 민족의 노하우를 일부 흡수해왔습니다
테스트가 뭔지는 알것 같은데 테스트로 개발하는 것은 뭐지? 이런 부분 까지는 모르는 상황에서 일단 Controller Layer 에 대해서 테스트를 진행했고 이번에는 전체적인 개발에 있어 테스트 기반으로 시작했고 그 나름의 가이드를 문서화 했습니다
예제로 쓰인 코드는 사내 깃랩에 오픈했고 필요하시면 권한을 획득해서 접근할 수 있습니다
현재 스프링 부트 테스트(JUnit5) 기반의 내용으로만 되어 있어 도입기 3에는 테스트 기반 개발 방법 보충하기 가 될 것 같습니다 (3부작)
여기에는 지금 담지 못한 일부 내용들이 추가될 것 같습니다
- Kotlin을 위한 KoTest 사용에 대해
- Kover 로 Kotlin 기반에서 테스트 커버리지 측정하기 (vs JaCoCo)
- Kotest, kover, gitlab-ci 로 테스트 자동화로 배포까지 하기
세세하게 모두 남길 수는 없어서 상세한 내용은 개발 가이드 문서인 컨플루언스에 남기고 아래 부분은 용어와 구현 방법 위주로 기록합니다 (한번 보시고 꼭 아래와 같은 방법이 아니어도 코드로 테스트 할 수 있는 방법이 필수가 되야 한다는 부분이 전달되었으면 합니다)
목차는 아래와 같습니다
- 테스트 코드란
- 테스트 도구
- 테스트 더블이란
- TDD와 BDD
- 테스트 의 종류
- Mock 에 대해
- 통합 테스트
- API 테스트
- 서비스 테스트
- 슬라이스 테스트
- Mock API 테스트
- Mock 서비스 테스트
- Mock Repository 테스트
- 외전: Mybatis 테스트
- POJO 테스트
- 테스트 커버리지와 테스트 자동화
- JaCoCo
- Gitlab-ci
테스트 코드란?
- 제품 or 서비스의 품질을 확인하거나 버그를 찾을 때 작성하는 코드들을 의미합니다
- 제품이 예상대로 동작 되는지 확인할 수 있고 자동화시켜서 지속적인 품질 수준을 유지하거나 높입니다
테스트 도구는 무엇이 있을까요? (JVM 진영 기준입니다)
- 테스트 라이브러리
- JUnit : 전통적 강자입니다
- KoTest : 신흥 강자입니다. 코틀린 진영에서 많이 사용합니다
- Spek : 코틀린 전용입니다 - 오픈소스가 활동적이지는 않습니다
- Spock: BDD에 특화되어 있습니다. 단 Groovy를 배워야 합니다
- Mock 을 위한 도구
- MockK(Mock) : 코틀린 전용 Mockito 입니다
- SpringMockK : MockK에서 @MockBean, @SpyBean 이 없어서 추가하게 되는 라이브러리입니다
- AssertJ : Assertion(단언문) 을 작성하기 위한 라이브러리로 에러나 예외처리르 담당합니다
테스트 더블이란?
- 테스트를 진행하기 어려운 경우 이를 대신해 테스트를 진행할 수 있도록 만들어 주는 객체 입니다
- Stub, Spy, Mock 이렇게 구분은 있지만 임시 객체라는 큰 맥락에서는 같다고 보시고 쉽게 접근합니다
- 테스트 더블의 종류
- Dummy : 인스턴스화 된 객체는 필요하지만 기능은 필요하지 않은 경우
- Fake : 복잡한 로직에서 내부에서 필요로 하는 다른 외부 객체의 동작을 임의로 단순화 한 경우
- Stub : 테스트에서 호출된 요청에 대해 미리 준비해둔 결과를 제공하는 경우
- Spy : Stub의 역할에 추가해서 실제 객체의 메소드를 호출까지 추가하는 경우
- Mock : 호출에 대한 기대하는 상황을 명세하고 이에 따라 동작되는 껍데기 객체를 만드는 경우

TDD 란?
- 테스트를 먼저 작성하고 그 뒤에 테스트 케이스를 통과하는 코드를 작성하느 방식으로 테스트가 주도하는 개발 방법입니다
BDD 란?
- BDD는 (Behavior Driven Development )로 TDD를 근간으로 파생된 개발 방법 입니다
- TDD에서 한 발 더 나아가 테스트케이스 자체가 요구 사양이 되도록 하는 개발 방법 입니다
- BDD를 통해 개발을 하게 된다면 테스트 메소드의 이름을 "이 클래스가 어떤 행위를 해야한다 (should do someting)" 라는 식의 문장으로 작성하여 행위에 대한 테스트에 집중할 수 있습니다
- BDD는 시니리오를 기반으로 테스트 케이스를 작성하고 함수 단위 테스트를 권장하지 않습니다
- 이 시나리오는 개발자가 아닌 사람이 봐도 이해하는 레벨을 추구합니다
- 하나의 시나리오는 Given, When, Then 구조를 가지는 것으로 기본 패턴을 권장합니다
- Feature : 테스트에 대상의 기능/책임을 명시합니다
- Scenario : 테스트 목적에 대한 상황을 설명합니다
- Given : 시나리오 진행에 필요한 값을 설정합니다
- When : 시나리오를 진행하는데 필요한 조건을 명시합니다
- Then : 시나리오를 완료했을 때 보장해야 하는 결과를 명시합니다
- 간단히 생각하면 BDD 는 TDD를 어떤 방식으로 할지에 대한 구체적인 제시라고 할 수 있습니다
테스트의 종류는 무엇인가요?
- 테스트 전략
- 통합 테스트
- 개발자가 변경할 수 없는 부분(외부) 까지 묶어 검증할 때 사용할 수 있습니다
- 단위테스트에서 발견되지 않는 에러 부분을 확인할 수 있습니다
- 시나리오 테스트(Acceptance Test)
- 사용자 시나리오에 맞춰 수행하는 테스트(비즈니스에 초점을 둔다) 입니다
- 누가, 어떤 목적으로, 무엇을 하는가에 대한 관점입니다
- 개발하다 보면 API를 통해 이런 의미가 드러나고 이런 API를 시나리오에 맞게 확인하는 방식으로 이뤄집니다
- 명세대로 잘 동작하는지 검증하는 부분으로 블랙박스 테스트를 기본으로 합니다
- 통합 테스트

시나리오 테스트 예시 - RestAssured
통합 테스트에 대해
- 장점
- 모든 Bean을 올리고 테스트를 진행하기 때문에 쉽게 테스트가 가능합니다
- 모든 Bean을 올리고 테스트를 진행하기 때문에 운영 환경과 가장 유사하게 테스트가 가능합니다
- API를 테스트 할 경우 요청 부터 응답까지 전체적인 테스트를 진행 가능합니다
- 단점
- 모든 Bean을 올리고 테스트를 진행하기 때문에 테스트 시간이 오래 걸립니다
- 테스트의 단위가 크기 때문에 테스트 실패시 디버깅이 어려웁니다
- 외부 API 요청같은 Rollback 처리가 안되는 테스트를 진행할때 어렵습니다
- 통합 테스트에 쓰이는 Annotation 종류
- @SpringBootTest
- @AutoConfigureMockMvc
- API 테스트
- 장점
- 주로 컨트롤러 테스트를 주로 하며 요청 부터 응답까지 전체 플로우를 테스트 할 수 있습니다
- 단점
- 초기 데이터베이스 셋팅이 필요한 부분에 대해 하나하나 셋팅이 되어야 합니다
- Kotlin DSL 이 잘 되어 있긴 하지만 response 를 검증할 때 라이브러리 사용방법을 익혀야 제대로 사용할 수 있습니다
- 장점

- @SpringBootTest 의 Base 클래스인 SpringTestSupport 를 상속받습니다
- 통합 테스트에 필요한 기능을 protected 를 통해 제공해 줄 수 있습니다
- 유틸성 메소드도 공통 클래스로 추가해서 테스트 코드의 편의를 높일 수 있습니다
실제 사용 예시(컨플루언스)
- 서비스 테스트
- 장점
- 통합 테스트의 Base 클래스로서 일관성있는 상속으로 테스트를 처리할 수 있습니다
- 단점
- DB 까지 연결 됨에 따라 해당 환경에 의존성이 있습니다
- 장점

- @TestConstructor 를 통해 테스트 코드에서 생성자 주입이 가능해졌습니다
실제사용 예시
Mock 에 대해
- 다양한 테스트 상황을 위해서 임의이 가짜 객체를 만드는 것을 말합니다
- Mock의 검증 순서는 아래의 패턴을 반복합니다
- Mock 만들기 (create)
- Mock 동작 지정 (stub)
- Mock 의 사용 (test)
- 검증 (verify)
- Mock 사용 시 주의할 점 및 적절한 사용 방법
- 실제 환경에서는 제대로 동작하지 않을 수 있습니다
- Mock 을 사용한다면 성공을 의도하고 테스트를 작성할 수 있어 완벽한 테스트로 보기는 힘들게 됩니다
슬라이스 테스트에 대해

사용하는 테스트와 구현 클래스
- 슬라이스 테스트의 Annotation 종류
- @WebMvcTest
- @DataJpaTest
- @MybatisTest
- @JsonTest
- @RestClientTest
- Mock API 테스트
- 장점
- 통합 테스트 보다 빠르게 테스트 할 수 있습니다
- 통합 테스트를 진행하기 어려운 테스트를 진행합니다
- 외부 API 같은 Rollback 처리가 힘들거나 불가능한 테스트를 주로 합니다
- 예를 들면 외부 결제 모듈 API를 호출해서 블랙박스를 Mock으로 치환해서 이용해야 할 때 사용할 수 있습니다
- 외부 API 같은 Rollback 처리가 힘들거나 불가능한 테스트를 주로 합니다
- 단점
- Mock 기반으로 테스트하기 때문에 실제 환경에서는 제대로 동작하지 않을 수 있습니다
- 장점

- @TestEnvironment 로 테스트 Profile 을 주입 받습니다
- MockMvc 의 경우는 setup 과정을 거쳐서 설정을 하고 ObjectMapper 는 @Autowire 로 의존성 주입을 받습니다
- @RestDocsConfiguration 을 추가해서 RestDocs 기능을 추가할 수 있습니다
- Swagger-UI 를 쓰면서 테스트를 같이 할 수 있고 Rest-Doc 을 안 쓰면서 Mvc 테스트를 할 수 있습니다
- API 개수가 많아진다면 Rest Docs 의 정적 페이지로는 한계가 있습니다
- Rest Docs 웹 페이지를 도메인에 따라 분리하거나 Swagger-UI로 많은 API 목록을 슬라이드 동적 기능으로 처리하고 Controller Layer 의 단위 테스트를 따로 하는 것도 고려해 볼 수 있습니다
실제사용 예시
- Mock 서비스 테스트
- 장점
- 진행하고자 하는 비즈니스에만 집중해서 진행할 수 있습니다
- 중요한 관점이 아닌 것들은 Mocking 해서 외부 의존성을 줄일 수 있습니다
- 단점
- 의존성있는 객체를 Mocking 하기 때문에 온전한 테스트가 아닙니다
- Mocking 하기가 귀찮고 이 라이브러리에 대한 선행 학습이 필요합니다
- 장점

- 주로 Service 영역이나 Adaptor 영역을 테스트 합니다
- Spring Layer 는 아니지만 다양한 라이브러리들의 간단한 기능 단위 테스트를 할 수 있습니다
- Mocking 기반의 테스트를 합니다
- 코틀린에서는 Mockito 가 아닌 MockK 를 사용합니다
실제사용 예시
- Mock Repository 테스트
- 장점
- Repository 관련된 Bean 들만 등록하기 때문에 통합 테스트에 비해서 빠릅니다
- Repository 에 대한 관심사만 가지기 때문에 테스트 범위가 작습니다
- 단점
- 테스트 범위가 작아서 실제 환경과 차이가 발생할 수 있습니다
- 장점

- @DataJpaTest 로 JPA 관련 Bean 만 로드 합니다
- 테스트가 끝날 때마다 자동으로 테스트에 사용한 데이터를 롤백합니다
- @TestEnvironment 로 Test Profile 을 주입받습니다
- @AutoConfigureTestDatabase 로 DB 설정을 합니다
- Replace.ANY 를 사용하면 기본으로 내장된 임베디드 데이터베이스를 사용합니다
- Replace.NONE 을 사용하면 profile 에 지정된 데이터소스를 사용합니다
실제사용 예시
외전 : JPA 가 아닌 Mybatis 를 쓰고 있어요!!
CRM 의 레거시에는 JPA 는 있지 않습니다 사실 단일화 된 데이터베이스 연결 기술이라는 부분보다는 JDBC 와 관련된 다양한 라이브러리들을 기반으로 어플리케이션 개발하는 일이 더 많을 것으로 생각됩니다
실제 KCRM에서 Mybatis 단위 테스트한 코드를 예제로 담았습니다 여기서 사용한 Test Annotation은 Mock Service 로 만든 @SpringServiceTestSupport 입니다
Port and Adapter 패턴을 따르고 있기 때문에 SQL 이 아닌 NoSQL 연결이 생기더라도 일관성있는 테스트 행위가 필요합니다
그렇기 때문에 라이브러리에 종속되는 Annotation 을 쓰기보다는 어느 Layer 에서 테스트가 이루어 지는지 구조적인 관점에서 범용성을 가져야 한다고 생각했습니다

Mybatis 인지 JPA 인지가 중요하지 않게 되었습니다
검증하려는 기능에 맞게 Given - When - Then 패턴으로 확인 하면 됩니다
분량 떄문에 모든 테스트 케이스에 따른 예제를 담지는 못했는데 전반적인 내용은 위 Mybatis 와 비슷합니다
- POJO 테스트
- 객체의 기능에 대한 테스트를 진행합니다 (Entity, DTO, VO, etc.. )
- 객체지향에서 본인의 책임(기능)은 본인 스스로 제공해야 합니다
- JPA 기반에서는 Entity로 대표되지만 일반적인 POJO 기반의 Test를 통칭합니다
- Entity 대상이 테스트 될 수 있고 Utility 클래스가 대상이 될 수 있고 Domain 모델이 대상이 될 수 있습니다
- 장점
- 외부에서 주입받을 의존성이 없어 Mocking 에 대한 대상도 없습니다
- Entity 객체는 사용하는 계층이 많으므로 테스트의 효율성이 높습니다
- 단점
- 부분 적인 객체에 대해서만 테스트가 되기 때문에 통합적인 관점에서 비즈니스를 대표할 수는 없습니다
- 장점

- DTO 에서 쓰이는 Spring Validation 기능을 테스트 하기위해 Validator 를 주입 했습니다

- POJO 의 생성을 담당하는 Builder는 따로 구현 했습니다 (기본값 지정)

테스트 커버리지와 테스트 자동화
- 더 나은 코드를 만들기 위해서는 피드백이 필요합니다
- 한번에 완벽하게 해결하기를 바라는 것보다 점진적 개선에 만족하고 피드백을 통해 점점 완벽에 다가 갑니다
- 우아한 테크 코스 교육 방침: 코드 커버리지(Code Coverage)가 뭔가요?
코드 커버리지란?
In computer science, test coverage is a measure used to describe the degree to which the source code of a program is executed when a particular test suite runs. A program with high test coverage, measured as a percentage, has had more of its source code executed during testing, which suggests it has a lower chance of containing undetected software bugs compared to a program with low test coverage. - wikipedia
- 소프트웨어의 테스트 케이스가 얼마나 충족되었는지를 나타내는 지표 중 하나입니다
- 테스트를 진행했을 때 코드 자체가 얼마나 실행되었느냐는 것이고 이를 수치를 통해 확인할 수 있습니다
코드 커버리지의 측정 기준
- 소스 코드를 기반으로 수행하는 화이트 박스 테스트를 통해 측정합니다
- 블랙 박스 테스트
- 소프트웨어의 내부 구조나 작동 원리를 모르는 상태에서 동작을 검사하는 방식입니다
- 올바른 입력과 올바르지 않은 입력을 입력해서 각각 상황에 맞는 출력이 나오는지 테스트 하는 기법입니다
- 사용자 관점의 테스트 방법이라 볼 수 있습니다
- 화이트 박스 테스트
- 응용 프로그램의 내부 구조와 동작을 검사하는 테스트 방식입니다
- 소프트웨어 내부 소스 코드를 테스트 하는 기법입니다
- 개발자 관점의 단위 테스트 방법이라고 볼 수 있습니다
- 블랙 박스 테스트
코드 커버리지가 왜 중요하죠?
- 코드 커버리지의 중요성은 테스트 코드의 중요성과 같습니다
- 테스트 코드는 발생할 수 있는 모든 시나리오에 대해 작성되어야 합니다 그런데 개발자도 사람인지라 테스트로 커버하지 못하는 부분이 발생될 수 있습니다
- 이럴 때 테스트에서 놓칠 수 있는 부분들을 코드 커버리지를 통해 눈으로 확인할 수 있습니다
- 코드 커버리지는 휴먼에러를 최대한 방지할 수 있도록 도와주는 용도라고 생각해도 될 것 입니다
코드 커버리지를 활용하는 방법
- 코드 커버리지와 소나큐브와 같은 정적 코드 분석을 함께 활용해서 코드 커버리지가 기존보다 떨어지는 경우 커밋이 불가능하도록 제한할 수도 있습니다
- 이처럼 코드 커머비지는 코드의 안정성을 어느정도 보장 해 줄 수 있는 지표이기 때문에 테스트 코드의 중요성을 느낀다면 휴면에러를 내지 않기 위해서라도 코드 커버리지를 측정하고 발전시키려고 노력해야 합니다

Test 단계에서 실패된 CI pipeline 예시 (Mauve point)
JaCoCo 를 통한 테스트 커버리지 측정

예제 프로젝트 코드 커버리지 : 82% 달성
테스트 코드 작성에서 끝이 아니라 테스트 자동화를 통한 빌드와 배포까지 적용할 수 있어야 어플리케이션의 품질을 보장할 수 있는 수단이 될 것 으로 보입니다
현재 CRM 파트에서 운영하는 어플리케이션에는 테스트 코드와 코드 커버리지를 통한 CI/CD 레벨의 테스트 자동화를 도입해보고 있습니다
여기에 코드 품질(커버리지 수치 높이기, 코드 리뷰 하기, (신규개발자, 기존 인원을 위한)개발 문서 작성 등과 같은 SW 개발의 완성도를 높이는 방법들을 도입해보려 하고 있습니다
아직 걸음마 단계라 많은 피드백과 제안들은 언제나 환영입니다!! (문서 및 코드)
내용이 길어질 것 같아 깨달음에 대한 내용 보다는 인용 부분이 많습니다 추가로 참조 내역을 남깁니다
컨플루언스에는 개발 가이드로서 발전되도록 꾸준히 업데이트 할 예정입니다
Reference
- 김남윤 개발자 - 테스트
- 최범균 개발자 - 테스트
- 박재성 개발자 - 우아한 형제들 개발자 교육 담당
'My Work > Tech Blog' 카테고리의 다른 글
| Spring Boot 3.0 Release Notes (0) | 2025.10.12 |
|---|---|
| 서버의 Error 에 따른 HTTP Status Code (0) | 2025.10.12 |
| 테스트 기반 개발 방법 도입기 1 (0) | 2025.10.12 |
| 코드 리뷰 - 잘할 수 있을까? (0) | 2025.10.12 |
| Ktor를 써 봤습니다 (0) | 2025.10.12 |