회고/TIL

출처 불분명스트레스, 테스트에 관하여, @Mock, @MockBean

KEEMSY 2023. 8. 16. 23:53

오늘은 정말 힘든 하루였다. 정말 왠만해선 짜증을 내지 않는 내 자신이 원인모를 짜증으로 정말 힘들었다. 아직도 원인을 잘 모르겠는데, 지금 오늘 하루를 기록하는 이 순간에도 속어디선가 화가 들끓는다... 운동으로 풀어보려했으나, 풀리지 않았다.. 개발로 풀어보려했으나 개발로도 풀리지 않았다.. 오히려 운동도, 개발도 모두 잘 안됬다. 지금 돌이켜보면 정말 많이 쉽지 않은 하루였던 것 같다.

 


테스트에 관하여

 

오늘은 테스트에 하루개발시간을 거의 다 소모했다. 컨디션 문제도 있었지만, 테스트와 관련하여 이슈와 고민때문에 아주 많은 시간을 소비하게 된 것 같다.

 

오늘은 Application 계층의 API 를 모두 구현하고, 테스트하는 것이 목표였다. API 는 Order 생성, Order 조회 이었기 때문에 매우 간단했기 때문에 충분히 테스트를 진행하며 개발을 이어 갈 수 있을 것이라 생각했다. (하지만 테스트에서 발목을 잡혔다지)

 

 


테스트를 하는 클래스에서 사용하는 클래스는 어떻게 해야할까?

 

나는 테스트를 진행하는 클래스에서 사용하는 (의존성 주입된)모든 클래스는 Mocking하여, Stub 처리하는 것이 맞다고 생각하고 있다. 그런데 오늘 큰 고민이 된 부분이 생겼다. 특정 클래스의 처리를 위임한 클래스라면?

 

나는 CommandService 를 개발하면서 이 고민이 시작되었다.

 

CommandService 의 모습, CommandService 에서는 Command(상태변경)의 유스케이스를 다룬다.

나는 유스케이스를 크게 Command(상태변경), Query(조회)로 분류하고, 각 서비스에서 이를 처리하도록 하였다. 서비스는 1차 분류된 유스케이스에 따라 또 다시 Handler로 나눠진다. 즉, 각 유스케이스는 최종적으로 Handler 에서 다뤄진다.

 

주문 생성 유스케이스를 담당하는 CreateOrderCommandHandler

주문 생성 유스케이스를 담당하는 CreateOrderCommandHandler 는 가독성을 위해 OrderCreateHelper 클래스를 생성자를 통해 주입받고, createOrder 를 간단하게 표현한다.

 

OrderCreateHelper 의 생성자 부분

 

이제 주문 생성 유스케이스를 구현하기 위해서는 OrderCreateHelper 클래스를 먼저 테스트하고 기능을 구현해야한다. 그리고 나는 여기서 많은 고민을 하게 되었다.

 

내가 작성했던 OrderCreateHelper 클래스의 persistOrderTest
테스트를 작성하면서 구현한 persistOrder 메서드

 

가만보면, 사용하는 모든 클래스를 Mocking 및 Stub 하고있다. 해당 로직에서의 과정들을 모두 Stub(내가 원하는 답을 작성) 하는데 이것이 의미가 있는걸까? 하는 생각이 들었다. 그러면서 동시에, 내가 테스트를 하고자하는 목적이 뭐지? 이 메서드를 개발하는데 테스트를 잘못작성하는 것은 아닌가? 하는 생각이 들면서 이글의 처음으로 돌아가고 다시 지금으로 돌아기를 반복했다.

 

결국 나는 이 테스트가 불필요하다 판단했다. CommandService에서 CommandHandler 를 호출하고, CommandHandler에서 Helper 를 호출하면서 자연스럽게 검증이 되어 불필요하다 생각했기 때문이다. 하지만 한편으로는 이렇게 내가 원하는 데이터들로 Mocking 및 Stub 하더라도 작성하는 것이 맞는것인지... 헷갈린다..

 

그렇게 나는 OrderCommandService 테스트를 작성하며, OrderCommandHandler 를 개발했다.

 

OrderCommandService 구현에 작성된 테스트

 

중간중간 솓구치는 화때문에 정말 힘들었지만, 하나의 유스케이스를 개발하는데 거진 4시간이 걸렸다.. 하지만 화는 여전히 계속됬고, 이후 OrderQueryService 를 구현하는데에도 큰 영향(?)을 주었다.

 

 


OrderQueryService 테스트간 발생한 오류

 

OrderQueryService 또한 개발해야 할 API는 주문 조회 기능 한가지 이다. QueryService 는 이전 CommandService에서 고민했던 부분들이 없어 진짜 10분도 안걸리게 개발할 수 있을것이라 생각했다. 그리고 언제나 이러한 거만은 화를 불렀다.(오늘은 안그래도 넘쳐 흘렀는데 말이다..)

 

내가 작성한 trackOrder 테스트

 

 CommandService 에서는마찬가지로 Handler에 해당 유스케이스를 다루도록 책임을 위임한다. 그리고 나는 Handler 를 Stub 하여, 원하는 데이터를 가져올 것으로 예상하며, 테스트 코드를 작성했다.

 

그리고 TrackOrderQueryHandler 에서 trackOrder 를 통해 상호작용했음을 검증하는 것으로, 내가 원하는 동작을 할 것임을 검증하고자 하였다. 하지만 문제가 발생하였다.

 

trackOrder() 이 호출되지 않아 발생한 오류

테스트 실행결과는 정말 당황스러웠다. Hanlder내의 trackOrder() 가 호출이 되지 않는다니..? 말이 되지 않는다. 분명 내가 작성한 QueryService 에서는 Handler 의 trackOrder() 를 호출하는데... 너무 이상했다.

 

분명하게 호출되는 trackOrderQueryHandler의 trackOrder 메서드

 

나는 바로 Print 를 통해 확인해 보았다.

TrackOrderQueryHandler 에서 작성한 print문이 정상적으로 출력된다.

 

나는 더 큰 의문에 빠졌다. 정상적으로 해당 메서드를 호출하는데, 호출되지 않았다니..? 또 다시 한참을 고민하고 코드를 다시 차분히 보면서 문제의 원인을 알 수 있었다.

 

 

나는 @SpringBootTest 를 하면서, @Mock 을 사용하고 있었다.

 

그냥 문득 SpringBootTest 인데 왜 MockBean 을 사용하지 않았지? 하는 생각이 들어 Mock 부분을 MockBean 으로 변경해보았다. 그리고 신기하게 문제는 해결되었다.

 

나는 둘의 차이를 확인하기 위해 각각 상황에 맞춰 trackOrderQueryHandler 를 print 해보았다.

 

@Mock 을 사용했을 때 결과
@MockBean 을 사용했을 때 결과

 

결과는 크게 차이가 없었다.(?) 그래서 해당 차이가 궁금해서 GPT에게 물어보았다.

 


@Mock, @MockBean

 

GPT의 답변

 

요약하자면, @Mock 은 Mockito 기반의 단위테스트에서 Mock 객체를 만들 때 사용되며, @MockBean 은 Spring Boot 통합 테스트에서 Spring MockBean 을 생성하는데 사용된다. 이 둘 사이의 선택은 작성중인 테스트가 Spring 컨텍스트 내 구성 요소의 상호작용을 테스트하는지,  혹은 격리된 단위테스트에 집중하는지에 따라 달라진다고 한다.

 

다시 지난 QueryService 테스트를 본다면, 나는 Spring 컨텍스트 내 구성요소간의 상호작용을 테스트하기에 @Mock 이 아닌 @MockBean 을 사용했어야 했던 것이다.

 

이에 대한 내용은 처음 알게되어, 테스트에 관한 내용을 정리하면서 함께 작성해야겠다.

 


 

오늘은 정말 많이 속상하다. 내 자신에게도 화를 다스리지 못해 속상하고, 계획한 일정을 지키지 못해 속상하다. 내 자신에 반성하고, 내일은 오늘보다 나은 내일이 될 수 있었으면 좋겠다.

728x90