너무나도 많은 글들과 영상에서 지겹도록 테스트 코드의 필요성을 들었을 것이라 짐작한다. (때문에 이 글에서는 테스트 코드의 중요성을 이 단락 이상 말하지 않을 예정이다)
테스트 코드 작성의 장점을 극단적으로 진보시켜 취하고자 하는 개발 방법론이 TDD이고, 수 많은 테스트를 통해서 개발자가 요구 사항에 맞게 구현한 로직에 구멍은 없는지 자신할 수 있는 것이 테스트 코드의 목적, 알파이자 오메가라고 생각한다.
때문에 적어도 그 중요성을 아는 개발자는 테스트 코드를 작성하는 것이 더 이상 낯설지 않게 되었고, 수많은 아키텍쳐와 개발 방법론들의 홍수에서 그 장단점을 저울질하면서 최선의 선택을 하고 있다고 생각한다.
당연하게도 추구하는 방향과 목적성이 다르기 때문에, 몸 담고 있는 팀 또는 회사마다 코드 컨벤션이 다르듯이 테스트에 대한 컨벤션도 당연히 천차만별일 수 밖에 없다.
약 2년 정도 현업에서 일을 해보니 아무리 실력이 뛰어난 개발자라도 사람인 이상 실수를 할 수 밖에 없고 그 실수가 장애로 직결되는 등의 문제를 직접 겪을 수 있었다.
실수는 누구나 하지만, 똑같은 실수를 하지 않는 것이 중요하기 때문에 개발자는 테스트 코드를 작성한다.
커져가는 비즈니스와 도메인은 코드의 복잡성을 증가시킨다. 복잡한 코드를 테스트하기는 더 복잡해진다.
코드가 복잡할 수록 테스트 코드를 작성하는 것은 매우 어려워진다는 것이 문제이다.
내가 테스트 코드를 작성하면서 겪었던 어려움을 적어보자면 아래와 같다.
급작스럽게 특정 요구 사항이 프로덕션에 반영되어야 하는 경우가 왕왕 있다.
false
에서 true
로 바꾼다던지)무심코 지나가기 쉬운 경우이기 때문에, 테스트 코드를 이에 맞게 업데이트하는 것을 잊어버리는 경우가 있었다.
이러한 경우엔 작성한 테스트가 달성하고자 하는 목표를 제대로 달성하지 못하는 껍데기만 남은 죽은 테스트 코드가 된다.
마감 기한에 쫓겨 테스트 코드를 작성할 여유가 없는 경우도 왕왕 있다. 긴급하게 반영되어야 하는 경우 스테이징 환경에서 배포한 후 몇 가지 경우에 대해서 테스트를 제한적으로 진행해야 하는 경우가 속한다.
최근에 사내 밋업을 통해 알게 된 테스팅 라이브러리인 픽스쳐 몽키를 소개하고자 한다.
같이 검토했었던 라이브러리로는 AutoParams가 있지만, 달성하고자 하는 목표를 여러 부분에서 픽스쳐 몽키를 통해서도 달성할 수 있다고 판단하여 오늘 논의에서는 제외하고자 한다.
테스트 픽스쳐란 무엇일까?
픽스쳐 몽키는 테스트 픽스쳐를 생성해주는 라이브러리이다.
db 저장 없이 생성한 테스트 픽스쳐의 값을 테스트하는 코드를 아래 요구사항에 맞추어 작성해보자.
1. 계정이 탈퇴 가능한지 체크하는 api를 작성한다고 가정하자.
2. 계정의 status
가 ACTIVE
인 경우 작성한 글 목록을 조회한다.
3. 만약 작성한 글이 하나라도 존재하는 경우 계정 탈퇴 불가능하다.
4. ACTIVE
가 아닌 다른 status
를 가지는 계정의 경우 아무것도 수행하지 않고 탈퇴 가능하다.
private final ArbitraryBuilder<Account> accountBuilder = FixtureMonkey.builder()
.plugin(new JakartaValidationPlugin())
.build()
.giveMeBuilder(Account.class)
.set("accountId", Arbitraries.lazy(() -> Arbitraries.of(TEST_ACCOUNT_UIDS)))
.set("status", AccountStatusType.ACTIVE);
private final ArbitraryBuilder<Post> postBuilder =
@RepeatedTest(10)
void ACTIVE_아닌_계정_상태_테스트() {
Account testAccount = accountBuilder
.set("status", Arbitraries.lazy(() -> Arbitraries.of(AccountStatusType.INACTIVE, AccountStatusType.LEAVE))
.sample();
when(accountRepository.find(any()))
.thenReturn(Mono.just(testAccount));
//ACTIVE 상태가 아닌 계정은 바로 탈퇴 가능함을 검증
StepVerifier.create(accountService.isLeavable(testAccount))
.expectNextMatches(dto -> dto.isLeavable() == true)
.verifyComplete();
//PostRepository 통한 조회가 한번도 일어나지 않았음을 검증
verify(postRepository, never()).find(any());
}
@RepeatedTest(10)
void ACTIVE_계정_상태_게시글_존재_테스트() {
Account testAccount = accountBuilder.sample();
when(accountRepository.find(any()))
.thenReturn(Mono.just(testAccount));
when(postRepository.find(any()))
.thenReturn(Flux.fromIterable(
FixtureMonkey.create()
.giveMeBuilder(Post.class)
.sampleList(3)));
StepVerifier.create(accountService.isLeavable(testAccount))
.expectNextMatches(dto -> dto.isLeavable() == true)
.verifyComplete();
}
@RepeatedTest(10)
void ACTIVE_계정_상태_게시글_미존재_테스트() {
Account testAccount = accountBuilder.sample();
//게시글 검색 시 빈 Flux를 반환
when(postRepository.find(any()))
.thenReturn(Flux.empty());
when(accountRepository.find(any()))
.thenReturn(Mono.just(testAccount));
StepVerifier.create(accountService.isLeavable(testAccount))
.expectNextMatches(dto -> dto.isLeavable() == true)
.verifyComplete();
}
위와 같은 방법으로 테스트 픽스쳐를 간단하게 만들어, 핵심이 되는 비즈니스 로직만을 보다 간편히 테스트할 수 있게 되었다.
상기 테스트 코드를 작성하면서 체감할 수 있었던 굵직한 장점들은 아래와 같다.
Account
엔티티의 필드를 전부 채울 필요 없이, 랜덤하게 생성되는 값들을 기반하되 로직에 관여하는 필드만 임의로 세팅이 가능했다.INACTIVE
, LEAVE
상태를 굳이 구분지어 테스트 하기 위한 불필요한 테스트 코드가 줄어들었다. Post
테스트 픽스쳐를 생성하기 위해 고민을 많이 하지 않을 수 있었다. 랜덤하게 채워지는 값들은 constraint 제약 조건을 따라서 생성되기 때문이다.타 라이브러리와 같이 각자 프로젝트 상황과 코드 컨벤션에 따라 테스트 픽스쳐를 제한적으로 만들어야 하는 상황들이 다양할 것이다.
픽스쳐 몽키 라이브러리는 이를 해결하기 위해 여러 plugin들을 제공해준다.
사내 밋업에서 볼 수 있었던 다양한 예시는 코틀린 기반의 코드들이 많았다. 프로젝트를 kotlin을 사용해 개발한다면 테스트 픽스쳐 생성 부분에 있어 상당히 매끄럽게 진행할 수 있는 것 같았다.
(kotest plugin도 따로 존재한다..! 하지만 이부분은 아직 잘 모르기 때문에..)
spring의 기본 serializer / deserializer인 jackson 라이브러리와도 궁합이 좋다.
FixtureMonkey fixtureMonkey = FixtureMonkey.builder()
.plugin(new JacksonPlugin())
.build();
위와 같은 방식으로 플러그인을 적용한다면, 컨트롤러 레벨에서 @JsonIgnore
, @JsonProperty
와 같은 어노테이션의 동작또한 테스트할 수 있다.
위에서 언급했던 Jakarta Validation / Hibernate validator 플러그인이다. 테스트 픽스쳐의 필드 설정 시 validation을 만족하는 값만 랜덤하게 생성되도록 돕는다.
@Min
과 같은 어노테이션을 테스트하기 좋다.
항상 테스트 코드를 어떻게 잘 효율적으로 작성할 수 있을지에 대한 고민이 있는 개발자라면 적극적으로 활용해보면 분명 장점이 있을 것이라 느꼈다.
체감했던 장점을 다시 한번 요약하자면