티스토리 뷰

반응형

Random Port의 SpringBoot 테스트를 진행할 때 Transactional이 적용되지 않는 문제를 직면하여 이유를 간단히 알아보았습니다.

잘못된 내용이 있다면 지적 부탁드립니다.

 


@SpringBootTest의 Web Environment

SpringBootTest의 Web Environment 설정은 네 가지가 있다.

기본값인 MOCK은 mocking 된 서블릿(WebApplicationContext)을 로드하며 NONE의 경우는 서블릿 환경을 아예 제공하지 않는다.

 

반면 RANDOM_PORT 혹은 DEFINED_PORT 옵션을 사용하면 각각 임의의, 혹은 지정된 포트로의 실제 서블릿 환경(EmbeddedWebApplicationContext)을 로드한다.

 

이로 인해 RANDOM_PORT 혹은 DEFINED_PORT를 지정하였을 때만 내장 톰캣이 켜지고 포트가 할당되는 것을 확인할 수 있다.

 

 


 

Random Port 테스트 환경에서는 @Transactional이 적용되지 않는다.

 

스프링에서 제공하는 @Transactional 어노테이션을 테스트 클래스에 붙이면 각각의 테스트 메서드가 실행되고 난 뒤 수행 내용을 rollback 한다. DB 테스트에 많이 쓰이는 @JdbcTest는 @Transactional을 이미 내장하여 각각의 테스트 후 내용을 롤백해 준다.

 

그러나 실제 서블릿 환경에서의 테스트를 위해 @SpringBootTest(webEnvirontment = SpringBootTest.WebEnvironment.RANDOM_PORT)를 붙였을 때는 예상과 달리 rollback이 일어나지 않는다.

이로 인해 하나의 테스트 클래스에서 여러 테스트 메서드가 임의의 순서로 실행될 때 DB가 rollback 되지 않아 테스트가 실패하게 된다.

 

이러한 문제가 발생할 수 있는 간단한 이유는 공식 문서에서 찾아볼 수 있었다.

 

https://docs.spring.io/spring-boot/docs/1.5.7.RELEASE/reference/html/boot-features-testing.html

 

요약하자면 "RANDOM PORT 혹은 DEFINED PORT의 테스트 환경은 진짜 서블릿 환경을 제공하여 HTTP 서버와 클라이언트가 서로 다른 쓰레드에서 동작해 트랜잭션이 분리되어 rollback이 되지 않는다"라는 것이다.

 

테스트 도중 HTTP 서버와 클라이언트의 쓰레드가 다른 것이 무슨 상관이길래 @Transactional의 rollback이 제대로 작동하지 않는 것일까?

 

 


 

Random Port 상황에서의 Thread ID 확인하기

 

Java에서는 다음의 코드로 현재 코드가 작동 중인 쓰레드의 ID (TID)를 확인할 수 있다.

Thread.currentThread().getId();

 

이를 통해 @SpringBootTest의 RANDOM_PORT 환경에서의 테스트, 프로덕션 코드의 TID를 각각 확인해 보자.

 

서블릿 환경을 사용하는 테스트를 위해 @Controller가 붙은 컨트롤러 클래스에서의 쓰레드와 SpringBootTest의 메서드에서의 TID를 각각 출력하게 하였더니 다른 값이 출력되었다. 즉, 테스트가 돌아가는 쓰레드와 테스트를 위해 로드된 서블릿 환경이 작동하는 쓰레드는 서로 다르다.

 

 


 

다른 Thread에서는 Rollback이 불가능하다?

 

쓰레드가 다르면 @Transactional이 예상대로 동작하지 않는 이유는 무엇일까?

이는 스프링 트랜잭션에 대한 간단한 이해가 필요하다.

 

@Transactional을 통해 스프링 트랜잭션을 사용하면 우리가 직접 Connection 객체를 얻어올 필요 없이 Connection을 미리 만들어 보관해 두고 요청하는 작업 쓰레드에서 이를 할당받아 사용하게 된다.

이때 쓰레드마다 Connection 객체를 독립적으로 관리하고, 각각의 쓰레드는 여러 Connection 풀을 가져 쓰레드에 독립적으로 트랜잭션을 관리할 수 있고, 하나의 쓰레드가 동시에 여러 트랜잭션을 관리할 수 있다.

 

https://docs.spring.io/spring-framework/docs/3.0.0.M4/reference/html/ch12s03.html

 

여기서 정답이 나왔다.

쓰레드가 다르므로 사용하는 Connection 객체가 다르고,

Connection 객체가 다르기 때문에 @Transactional을 붙였지만 rollback이 불가능한 것이다.

 


 

정리

 

  1. @SpringBootTest(webEnvirontment = SpringBootTest.WebEnvironment.RANDOM_PORT)에서는 테스트 코드가 동작하는 쓰레드와 프로덕션 코드가 동작하는 쓰레드가 분리된다.
  2. @Transactional이 붙은 테스트에서 사용하는 Connection은 웹 서블릿에서 사용하는 Connection과 다른 Connection이므로 롤백이 되지 않는다.

 

틀리거나 보강할 부분이 있다면 지적 부탁드립니다.

반응형