이번 장바구니 미션에서 컨트롤러 테스트 코드를 짤 때 @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: 현재 테스트 메서드 이후
다만 애플리케이션 컨텍스트가 매번 다시 만들어진다는 점에서 비용이 크고, 테스트 속도가 느려질 수 있다는 단점이 존재한다.
'프로그래밍 > JAVA Spring' 카테고리의 다른 글
[Spring 스프링] HandlerInterceptor 알아보기 (0) | 2023.05.07 |
---|---|
[Spring 스프링] HandlerInterceptorAdapter 말고 HandlerInterceptor (0) | 2023.05.07 |
[Spring 스프링] Mock을 이용한 테스트 (0) | 2023.04.30 |
[Spring 스프링] Spring Core - Bean과 Configuration (2) | 2023.04.24 |
[Spring 스프링] Spring Core - IoC(Inversion of Control), DI(Dependency Injection) (0) | 2023.04.24 |