- DB를 사용하는 테스트는 여러모로 어렵다.
- 내장형 DB가 아니라면 테스트 대상 애플리케이션 밖에서 동작하는데다, 데이터가 테스트 수행마다 변경이 되면 일관된 테스트 결과를 보장해줄 수 없기 때문에.
- 오래전에는 dbunit 같은 도구를 이용해서 테스트 수행 전 후에 테스트용 db를 준비하는 것과 테스트 후에 이를 원래대로 돌려놓는 작업을 일일이 진행을 했어야 했음
- 그런 시절에 등장한 @Transactional 롤백 테스트는 정말 혁신적이었고 db가 사용되는 테스트를 편리하게 작성할 수 있고, 각 테스트가 고립되어 수행되는 것을 보장해주기 때문에 많은 인기를 끌어왔음
- 하지만 @Transactional 테스트는 테스트 수행 중에 단 한 개의 트랜잭션 경계만 사용이 되고, 그 경계를 테스트 메소드로 확장을 해도 문제가 없는 상황에서만 유효함
- JPA의 detached 상태 오브젝트의 변경이 자동감지 되지 않은 코드
- @Transactional 테스트에서는 정상 동작하게 보이는 현상이나, @Transactional이 동일 클래스의 메소드 사이의 호출에서는 적용되지 않는(스프링의 기본 프록시 AOP를 사용하는 경우라면) 문제
- JPA에서는 save한 오브젝트가 영속 컨텍스트에만 존재하고 db로 flush되지 않은 상태로 rollback되기 때문에 명시적으로 flush하지 않으면 실제 db 매핑에 문제가 있어도 검증하지 못한다는 문제 등을 들 수 있음
- 이런 단점에도 불구하고 장점이 압도적으로 많기 때문에 @Transactional 테스트를 적극적으로 권장
- 테스트용 DB까지 동작하는 단위 테스트(보기에 따라선 통합 테스트)를 작성할 수 있음
- 심지어 병렬 테스트 수행도 가능
- 테스트 코드 작성 속도도 빠름
- 단점으로 @Transactional 테스트에서 제대로 검증하지 않는 문제는 잘 인식해야됨
- 테스트 하나에서 두 개 이상의 트랜잭션 경계가 참여하는 테스트를 작성하는 경우 강제로 커밋 테스트로 만들고 테스트를 작성해야함
- 테스트를 웬만큼 잘 작성해도 애플리케이션 코드를 완벽하게 검증할 수 없는 사실을 인식하자
- 그래서 개발자가 작성하는 테스트, 단위 테스트나 통합 테스트 외에 실제 환경과 유사하게 환경을 구성하고 진행하는 인수 테스트, e2e 테스트, 혹은 http api 테스트 같은 것을 추가로 진행해야 함
- 코드에서 발생할 수 있는 전형적인 오류들은 코딩 가이드를 잘 작성해서 따르게 하고, 코드 리뷰에서 확인할 수 있도록 하고, 사용 가능하다면 각종 정적 분석 도구의 힘을 빌어서 어떤 작업을 수행하는 위치에 제한을 걸어주는 등을 통해서 검증이 되도록 해야 됨
결론
- 테스트 메소드 단위로 트랜잭션 경계가 만들어진다고 생각하면 자연스러운 결정이므로 @Transactional 테스트 코드에 사용하자