@Test internalfun `특정 테스트를 하기위해서는 외부 dependency가 필요하다`() { // 특정 테스트를 하기 위해서 많은 디펜던시가 필요하다. paymentRepository.save(Payment(BigDecimal.TEN)) memberRepository.save(Member("username", 10, Team("team-name"))) orderRepository.save(Order(BigDecimal.TEN)) } }
많은 디펜던시가 필요한 부분의 테스트 코드를 작성할 때는 많은 Repository를 주입 받아서 테스트를 진헹 해야합니다. 위 코드처럼 특정 구간의 서비스를 테스트하기 위해서는 Given절을 작성할 때 흔하게 발생합니다.
물론 테스트 코드이기 때문에 DI 받아야하는 항목들이 많아지는것이 상대적으로 문제가 크게 발생하지는 않지만 코드 양이 많이 발생하는 문제가 있습니다.
검증이 필요한데 ?..
무엇보다 큰 문제가 해당 테스트의 Then절에 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13
@Test internalfun `특정 테스트를 하기위해서는 외부 dependency가 필요하다`() { // 특정 테스트를 하기 위해서 많은 디펜던시가 필요하다. paymentRepository.save(Payment(BigDecimal.TEN)) memberRepository.save(Member("username", 10, Team("team-name"))) orderRepository.save(Order(BigDecimal.TEN))
// 특정 서비스가 여러 entity rows를 변경할때 아래와 같은 조회로 Then 이어가야 합니다. // paymentRepository.findBy... epository 메서드는 없는데??... // memberRepository.findBy... // orderRepository.findBy... }
해당 서비스의 코드가 여러 enttiy의 여러 row의 변경을 가할 때 Then절에서 검증을 진행할 때 문제가 발생합니다. 해당 조회 코드가 없는 경우 오직 테스트를 위해서만 조회용 코드를 일반 코드에 작성해야 합니다. 그렇지 않다면 Test Scope에서 사용할 Repository를 따로 작성해야 합니다.
@Test internalfun `entityManager를 이용해서 dependency가 최소화 `() { // 특정 테스트를 하기 위해서 많은 디펜던시가 필요하다. save(Payment(BigDecimal.TEN)) save(Member("username", 10, save(Team("team-ename")))) save(Coupon(BigDecimal.TEN)) } }
Entity의 영속성을 EnttiyManager를 통해서 진행하기 때문에 단순 save를 위해서 DI 받는 Repository가 없어졌습니다.
@Test internalfun `entityManager를 이용해서 dependency가 최소화 `() { // 특정 테스트를 하기 위해서 많은 디펜던시가 필요하다. save(Payment(BigDecimal.TEN)) save(Member("username", 10, save(Team("team-ename")))) save(Coupon(BigDecimal.TEN))
// 특정 서비스가 여러 entity rows를 변경할때 아래와 같은 조회로 Then 이어가야 합니다. // paymentRepository.findBy... epository 메서드는 없는데??... // memberRepository.findBy... // couponRepository.findBy...
val payments = query.selectFrom(QPayment.payment) .where(QPayment.payment.amount.gt(BigDecimal.TEN)) .fetch()
val members = query.select(QMember.member.age) .from(QMember.member) .where(QMember.member.age.gt(20)) .fetch()
val coupons = query.selectFrom(QCoupon.coupon) .where(QCoupon.coupon.amount.eq(123.toBigDecimal())) .fetch() } }
여러 엔티티의 여러 row의 수정을 진행했을 경우 해당 엔티티의 변경을 확인하기 위한 검증이 필요합니다. 이때 조회용 메서드를 단순히 테스트 코드에서만 사용하기 위해서 작성하기 위해서 작성하거나 Test Scope에 별도의 Repository를 구성해야 했지만 이제는 위에서 등록한 query을 이용해서 해당 비즈니스에 맞는 쿼리를 작성할 수 있습니다.
//then val count = query .selectFrom(qPayment) .where(qPayment.amount.gt(targetAmount)) .fetchCount()
then(count).isEqualTo(0) }
이런 식으로 심플하게 테스트 코드를 작성할 수 있습니다.
주의할 점
SpringBootTestSupport 클래스에서 정의한 save(), saveAll() 메서드를 사용하는 경우 테스트에서 사용하는 트랜잭션과 완전하게 분리가 됩니다.
스프링 테스트에서는 @Transactional이 있는 경우 해당 테스트 메서드가 종료되면 트랜잭션으로 같이 Rollback이 진행되게 됩니다. 하지만 SpringBootTestSupport에서 작성한 save(), saveAll()메서드 같은 경우에는 완전히 다른 트랜잭션이므로 given절에서 작성한 트랜잭션이 롤백 되지 않습니다.
마무리
테스트 코드의 중요성의 강조는 의미 없을 정도로 현업에서 자리 잡았다고 생각합니다. 이렇게 중요성이 있는 부분이니 테스트 코드를 작성하기 편한 방법도 많이 연구되었으면 좋겠다는 생각이 있습니다.