TDD를 실무에서 사용하다보니 문제를 찾았다. 실시간 데이터를 테스트할 때 자동화하기 어렵다는 문제를 발견했다. 처음에는 해결할 방법을 찾지 못해 실시간 데이터를
@BeforeEach
를 통해 모든 단위 테스트 별로 save하는 방식을 사용했는데, 이 방법에도 문제가 있었다.
매 단위 테스트는 @Transactional
어노테이션이 걸려 데이터는 롤백이 되지만 만약 AUTO_INCREMENT
방식을 사용한다면 인덱스 값에 공백이 생긴다는 문제와 시간이 많이 걸린다는 문제다.
이 방법은 최범균님의 테스트 주도 개발 시작하기에서 대역이라는 개념을 공부해서 해결해보려 한다.
테스트 대상에서 의존하는 요인 때문에 테스트가 어려울 때
대역
을 써서 테스트를 진행한다.
영어로는Test Double
테스트 대상이 이러한 외부 요인에 의존하면 테스트를 작성하고 실행하기가 어려워진다.
외부 요인이 테스트에 관여하는 주요한 예로는
이 있다. (내 경우는 두번째와 세번째에 해당했다.)
대역에는 종류가 있다.
주로 사용하는 Stub과 Mock을 코드로 확인해보자
이전에 일단 사용했던 Dollar 테스트 코드에 id
필드를 추가하고, 자주 사용하는 JpaRepository
로 먼저 시작해보았다. 기능 구현에 필요한 id와 생성자 부분을 추가했다.
테스트 폴더에 DollarStubRepository
를 생성한다.
implements
선언을 하고 Repository
어노테이션을 추가한다.
오버라이드를 일단 선언하여 빨간줄을 지운다. (java.lang.Object
의 내용만 제외하고 모두 오버라이드)
대부분의 기능은 null
을 리턴하거나 동작하지 않는 빈 메서드지만 주로 사용하는 내용들만 기능을 추가해본다.
가장 먼저 Map과 id를 필드로 선언
주요 메서드 동작 구현
saveAll()
과 findAll()
save
와 findById
, count
필요한 메서드를 구현하고 이전에 만들어놓은 DollarService
에 의존관계를 주입한다.
생성자 확인을 위해 StubRepository에 몇 데이터를 추가한다.
단위 테스트를 작성하고 실행
Stub
기능을 해봤지만 문제가 있다. 일부 기능을 위해 다양한 설정을 오버라이딩하고 복잡해도 Configuration
주입 설정을 해줘야 한다는 문제가 있다. 찾아보니 Stub
을 사용할 때 JpaRepository
보다는 필요한 기능만 사용하는 그냥 Repository
어노테이션을 선호하기도 한다고 한다.
다음과 같이 MockTest
코드를 만들었다.
@ExtendWith(MockitoExtension.class)
: Mock
을 사용하기위해 선언해 주어야 한다. (메인으로 실행될 Class
를 지정할 수 있음)@Mock
: Mock
사용 지정@InjectMocks
: 지정된 Mock
을 주입 받는 객체(TDD 원칙 중 given / when / then
으로 나누는 부분에서 Mock 기능은 given에 가깝다고 생각해서 BDDMockito
의 given
을 사용하지만 when
을 사용해도 상관 없다고 함)
실행해보면
바로 실패
근데 이상한 점을 발견했다.
@ExtendWith(MockitoExtension.class)
어노테이션을 제거하면 잘 동작한다.
반대로 @SpringBootTest
어노테이션을 제거하고 @ExtendWith
어노테이션을 살려도 동작한다.
둘 사이에 어떤 문제가 있는걸까?
@SpringBootTest
어노테이션의 세부 내용을 보면 다음과 같이 @ExtendWith
어노테이션이 있는데 두 같은 어노테이션이 충돌한 것으로 보인다.
@SpringBootTest
는
@SpringBootApplication
을 찾아가 하위의 모든 Bean 스캔 및 로드하는 역할을 하는 어노테이션이다.
오류의 원인도 찾았고, 테스트도 초록불이니 코드를 조금 정리해본다.
라는 의문에 내가 찾은 답에 간단히 정리해보면
Stub
은 준비한 가짜 데이터에 초점Mock
은 행위를 테스트하는 것에 초점Spy
는 일부 메소드에 대하여 Mocking
이 가능한 점이 Mock
과 다르다.
일부 Mock
데이터를 InjectMocks
주입받은 객체에서 사용할 때 Mock
데이터여서 오류가 날 경우 이 주입받은 객체에 @Spy
어노테이션을 추가해 실제 구현과 동일하게 동작하도록 만들 수 있다고 한다. (참고)
TDD 방식 중에 Classist
와 Mockist
가 있다고한다.
지금 나는 TDD 공부 이전에 간단히 하는 테스트는 Classist
에 가까웠지만 지금은 데이터를 만들기가 너무 어렵다는 이유로 Mockist
에 가깝운 상태로 진단된다. 잘 섞어서 어떤게 올바른 검증이고, 변동이 적은 자동화된 테스트임을 고민하면서 써야겠다.
부족한 부분은 댓글 달아주시면 공부하고 적용해보겠습니다. 읽어주셔서 감사합니다.