본문 바로가기

프로그래밍/테스트코드

인수테스트 격리

유스콘 마지막 과제를 진행하고 나서 제이슨님이 아래와 같은 질문을 주셨다.

인수테스트를 작성하셨는데 테스트 격리를 매번 테스트를 수행하고 초기화를 해주는 작업을 하신 것 같다. 선택하신 방법 외에 어떤 방법이 있는지?

나는 @Transactional 어노테이션을 쓰면 될 것 같다고 하였고 추가로 테스트 메서드에 어노테이션을 붙여주면 정상적으로 동작할 것 같냐는 질문에 나는 아래와 같이 답변하였다.

동작할 것 같다. 발생할 수 있는 문제로는 실제로 데이터의 변경이 일어나는 서비스 로직에 @Transactional 어노테이션을 붙이지 않고 테스트에만 붙이는 경우 테스트는 통과하는데 실제로 해당 로직에서 에러가 발생할 수도 있을 것 같다.

과연 정상적으로 동작하였을까? 한번 알아보도록 하자.

테스트 격리

인수테스트를 작성하다 보면 각각의 테스트는 잘 동작하지만 한꺼번에 테스트하는 경우 실패할 수 있다. 이유는 각각의 테스트가 자원을 공유하여서 생성, 수정, 삭제를 테스트하여 발생한 변화 때문에 조회에서 원하는 바와 다르게 조회가 될 수 있는데 테스트를 격리하여 이를 해결할 수 있다.

1. Truncate table

Truncate는 테이블 내부의 모든 행을 삭제한다. 따라서 매 테스트마다 Truncate table을 통해 데이터의 상태를 초기화하여 각각의 테스트마다 동일한 테이블 상태를 유지할 수 있다.

  • @sql 어노테이션 사용
    테스트가 실행되기 전에 해당 경로에 있는 SQL이 먼저 실행하고 TRUNCATE SQL을 작성해두면 미리 초기화를 할 수 있다.

엔티티가 추가될 때마다 sql파일을 수정해줘야 하지만 테스트에 따라 필요한 테이블들만 초기화하는 장점도 있을 것 같다.

  • EntityManager 사용
    스프링에서 제공하는 InitializingBean를 구현하여 afterPropertiesSet메소드를 통해 빈 객체 초기화 시 엔티티 매니저를 통해 테이블의 이름을 가져와서 초기화할 수 있다.

엔티티가 추가되어도 추가로 작성해야 할게 없어서 효율적으로 테스트를 격리할 수 있다.

2. @Transactional

테스트에서 @Transactional을 사용하면 테스트를 마치고 나서 데이터가 롤백이 된다. 하지만 여기에는 항상 롤백이 되지는 않는다.
내가 받았던 질문의 답은 spring docs에서 찾을 수 있었다.

If your test is @Transactional, it rolls back the transaction at the end of each test method by default. However, as using this arrangement with either RANDOM_PORT or DEFINED_PORT implicitly provides a real servlet environment, the HTTP client and server run in separate threads and, thus, in separate transactions. Any transaction initiated on the server does not roll back in this case.

REST Assured를 사용할 때 @SpringBootTest에 webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT로 하고 해당포트를 REST Assured의 포트로 사용하였다.
이처럼 RANDOM_PORT나 DEFINED_PORT를 사용하면 별도의 쓰레드에서 스프링 컨테이너가 실행되고 서로 다른 스레드에서 실행이 되게 되므로 트랜잭션이 롤백되지 않는다.

참고문서

spring docs

'프로그래밍 > 테스트코드' 카테고리의 다른 글

Test Double, Stub, Spy, Mock  (0) 2022.03.24