프로그래밍/JAVA Spring

[Spring 스프링] @SpringBootTest로 테스트할 때 데이터베이스 롤백하기

hectick 2023. 5. 7. 12:50

 

이번 장바구니 미션에서 컨트롤러 테스트 코드를 짤 때 @SpringBootTest어노테이션을 붙여서 짜면서 데이터베이스 롤백과 관련한 문제를 마주했다.

 

상품목록에 상품을 수정, 삭제하는 테스트 코드가 각각 있었는데, 항상 자동으로 수정 -> 삭제 순서로 테스트가 돌아가다가, @Order 어노테이션을 이용해서 삭제 -> 수정 순서로 돌려보니 테스트가 깨지는 것을 확인했다.

 

 

아무래도 이미 삭제된 상품을 수정하려고 하니 오류가 터진 것이겠군~ 디비 롤백을 해줘야겠네! 생각하고, 예전에 어디선가 @Transactional 어노테이션을 테스트에 붙이면 테스트가 끝난 후 변경된 내용들을 다시 롤백해준다고 들었기에 @Transactional 어노테이션을 붙여보았지만 여전히 테스트 코드는 깨졌다.

 

공식문서를 확인해보니 다음과 같은 내용이 있었다.

 

테스트가 @Transactional인 경우 기본적으로 각 테스트 방법이 끝날 때 트랜잭션을 롤백합니다. 그러나 RANDOM_PORT 또는 DEFINED_PORT와 함께 이 배열을 사용하면 암시적으로 실제 서블릿 환경을 제공하므로 HTTP 클라이언트와 서버는 별도의 스레드에서 실행되므로 별도의 트랜잭션에서 실행됩니다. 이 경우 서버에서 시작된 트랜잭션은 롤백되지 않습니다.

 

대충 @SpringBootTest로 테스트할 때, RANDOM_PORT나, DEFINED_PORT 환경에서는 클라이언트의 스레드와 서버의 스레드가 다른데, 여기서 @Transacional을 사용할 경우, 클라이언트쪽만 롤백하고, 서버쪽까지 롤백을 시키진 않는다는 내용인 것 같다.

 

나는 RANDOM_PORT를 사용했기 때문에 데이터베이스가 롤백이 되지 않았던 것이다!

 

그렇다면 어떤 방법으로 데이터베이스를 롤백 할 수 있는지 알아보자

 


 

1. @Sql 사용

 

프로덕션의 sql에 영향을 미치지 않게 하기 위해, test 패키지의 resources 안에 테스트 전용 testData.sql을 만들어 주었다.

 

 

main 패키지에 있는 data.sql과 동일한 sql을 사용하지만, 맨 위에 한줄을 추가해주어야 한다.

    DROP TABLE IF EXISTS product;

 

실제 testData.sql에 작성된 내용이다.

    DROP TABLE IF EXISTS product;

    CREATE TABLE product
    (
        id bigint PRIMARY KEY NOT NULL AUTO_INCREMENT,
        name varchar(255) NOT NULL,
        image text NOT NULL,
        price int NOT NULL
    );

    INSERT INTO product(name, image, price) VALUES ('mouse', 'https://cdn.polinews.co.kr/news/photo/201910/427334_3.jpg', 100000);
    INSERT INTO product(name, image, price) VALUES ('keyboard', 'https://i1.wp.com/blog.peoplefund.co.kr/wp-content/uploads/2020/01/진혁.jpg?fit=770%2C418&ssl=1', 250000);

 

그리고 테스트 코드에 아래처럼 @Sql("/testData.sql")어노테이션을 달아줘야 한다.

@Sql is used to annotate a test class or test method to configure SQL scripts() and statements() to be executed against a given database during integration tests.

 

이 어노테이션을 테스트 클래스나 메서드에 달아주면, 각각의 테스트가 실행될 때 특정 스크립트를 실행하여 테스트에 필요한 환경을 구성하도록 도와준다. 나의 경우엔 테이블이 존재할 경우 드롭한 후에 새로 만드므로, 데이터베이스를 초기 상태로 돌려놓도록 도와주는 것이다!

 

 

 

 

2. @DirtiesContext 사용

 

@DirtiesContext 어노테이션을 사용하면 테스트 클래스 또는 테스트 메서드가 수행된 후에 애플리케이션 컨텍스트 전체를 다시 만든다.

 

다음처럼 클래스 위에다가 붙여줄 수 있고,

 

 

각각의 메서드 위에 붙여줄 수도 있다.

 

 

 

어노테이션에 옵션을 주면, 컨텍스트가 다시 만들어지는 시기를 지정할 수 있다.

 

@DirtiesContext가 클레스 수준에 달린 경우, classMode의 옵션

  • BEFORE_CLASS: 현재 테스트 클래스 이전
  • BEFORE_EACH_TEST_METHOD: 현재 테스트 클래스의 각 테스트 메서드 이전
  • AFTER_EACH_TEST_METHOD: 현재 테스트 클래스의 각 테스트 메서드 이후
  • AFTER_CLASS: 현재 테스트 클래스 이후

 

@DirtiesContext가 메서드 수준에 달린 경우, methodMode의 옵션

  • BEFORE_METHOD: 현재 테스트 메서드 이전
  • AFTER_METHOD: 현재 테스트 메서드 이후

 

다만 애플리케이션 컨텍스트가 매번 다시 만들어진다는 점에서 비용이 크고, 테스트 속도가 느려질 수 있다는 단점이 존재한다.