프로젝트를 Hexagonal Architecture로 개발하면서, 외부 시스템과 소통하는 Port 인터페이스를 통해서 실제 비즈니스를 담당하는 UseCase의 구현체인 Service를 검증할 수 있다.
테스트코드 설계 단계에서 러프하게 짜둔 목표는 다음과 같다.
1. 애플리케이션 서비스(UseCase)에 대한 테스트는 Mock 기반으로 대체해서 단위 테스트
2. 실제 외부 시스템과 연결되는 Adapter 같은 경우는 통합 테스트로 진행
@Test
public void 이미_좋아요를_누른_사용자의_updateLikes(){
// given
when(findLikePort.isUserLiked(boardId, userId)).thenReturn(Mono.just(true));
when(findLikePort.getLikesCount(boardId)).thenReturn(Mono.just(2L));
when(removeLikePort.removeLike(boardId, userId)).thenReturn(Mono.empty());
// when
when(findLikePort.getLikesCount(boardId)).thenReturn(Mono.just(1L));
Mono<Long> result = updateLikeService.updateLikes(userId, boardId);
// then
StepVerifier.create(result)
.expectNext(1L)
.verifyComplete();
verify(removeLikePort).removeLike(boardId, userId);
verify(findLikePort).getLikesCount(boardId);
}
비즈니스 영역 안에서는 어떤 외부 시스템을 사용하는지 알 수 없으며, Port 인터페이스를 통한 느슨한 의존은 테스트를 좀 더 가독성 있고 쉽게 작성하게 해준다.
클린 아키텍처를 내부적으로 구현했다고 볼 수 있는 헥사고날 아키텍처가 왜 유지보수성이 뛰어난지, 왜 테스트 용이성이 뛰어나다고 하는건지 직접 적용해보며 체감할 수 있었다.
Reactor에서 비동기 Publisher 시퀀스(Mono나 Flux) 동작을 검증하는데 사용되는 도구
StepVerifier (reactor-test 3.7.3)
Prepare a new StepVerifier in a controlled environment using a user-provided VirtualTimeScheduler to manipulate a virtual clock via StepVerifier.Step.thenAwait(). The scheduler is injected into all Schedulers factories, which means that any operator create
projectreactor.io
reactor-test 의존성을 추가해서 적용할 수 있다.
testImplementation 'io.projectreactor:reactor-test:3.6.5'
verifyComplete() : 완료 이벤트가 발생해야 테스트 통과한다. 여기서 완료 이벤트는 onComplete()를 말한다.
expectComplete().verify() : Complete 이벤트를 예상하며, 전체 흐름을 검증한다.
Mono<Long> result = updateLikeService.updateLikes(userId, boardId);
StepVerifier.create(result)
.expectNext(10L)
.verifyComplete();
위 코드는 updateLikeService.updateLikes(userId, boardId)의 결과가 Mono<Long>이고, 그 값이 10L인지 확인한다
Spring WebFlux 환경에서는 비동기로 동작하기 때문에 JUnit만으로는 검증이 어렵다.
→ 비동기 흐름을 유지하면서 검증
block()을 사용하면 테스트 가능하지만, 블로킹 방식이라 WebFlux의 비동기 철학과 맞지 않음
Subscriber의 동작을 검증하기 위해서 직접 Publisher를 구현해야하며, 백프레셔를 테스트하거나 특정 이벤트에 대한 동작을 검증할 수 있다.
reactor-test 라이브러리에서 제공하는 TestPublisher를 통해 다양한 이벤트를 발생시키는 Publisher를 생성할 수 있다.
Reactive Stream을 통해 스트림 형태의 데이터를 처리할때 발생하는 upstream과 downstream의 속도 차이를 조절한다.
요청이 있었는지 백프레셔를 검증하는 메서드 : assertWasRequestd(), assertMinRequested(long n) , assertMaxRequested(long n)
외에도 emit(), next(), complete() 등의 데이터 방출과 구독 확인에 대한 기능을 제공해서 추상화된 스트림 데이터 처리를 검증할 수 있다.
@Test
void testPublisher(){
TestPublisher<Integer> publisher = TestPublisher.create();
publisher.subscribe(new Subscriber<Integer>() {
@Override
public void onSubscribe(Subscription s) {
s.request(10);
}
@Override public void onNext(Integer o) { }
@Override public void onError(Throwable t) { }
@Override public void onComplete() { }
});
publisher.assertWasRequested();
...
}
publisher.assertWasRequested();
publisher.assertMinRequested(10);
publisher.assertMaxRequested(10);
subscribe() : Publisher에게 구독자(subscriber) 등록을 요청한다
assertWasRequested() : upstream으로부터 데이터 요청이 있었는지 확인한다(min, max)
publisher.emit(1, 2);
publisher.assertWasNotCancelled();
emit(1,2) : 데이터를 방출한다.
assertWasNotCancelled() : 구독이 취소되지 않았는지 확인한다.
publisher.assertSubscribers(1);
publisher.assertNoSubscribers();
assertSubscribers(1) : 구독자가 1명 있는지 확인한다
assertNoSubscribers() : 구독자가 없는지 확인한다. (구독자가 남아있기 때문에 오류가 난다.)
| Tomcat, Netty의 쓰레드 덤프 분석을 통해 병목 지점을 분석해보자 - Prometheus,Grafana를 통한 시각화 모니터링 (1) | 2025.02.24 |
|---|---|
| 스프링에서 제공하는 Redis 직렬화/역직렬화 도구의 한계와 직접 구현 (Snappy 압축) (0) | 2025.02.19 |
| 성능 테스트와 개선을 위한 시도와 실패들 (0) | 2024.12.17 |
| Spring의 비동기 프로그래밍 @Async에 대해 알아보자 (1) | 2024.11.23 |
| Spring Cloud를 뜯어보자 (Gateway, Config, Netflix Eureka) (0) | 2024.11.19 |