테스트 - 데이터베이스 연동
테스트를 할 때는 기본적으로 src/test/resources
에 있는 application.properties
파일이 먼저 실행된다.
만약 실제 서버에서 연결할 DB와 테스트용 DB가 분리되어 있다면 해당 파일에 따로 값을 명시해줘야 한다.
@SpringBootTest
테스트를 진행할 때는 테스트용 클래스에 @SpringBootTest
를 붙여주는 것이 중요하다.
클래스에 @SpringBootTest
를 붙이다면 @SpringBootTest
는 @SpringBootApplication
를 찾아서
설정으로 사용하게 된다.
테스트를 실패하는 원인
개발 초기이고 특정 서비스를 본인이 혼자 개발한다면 테스트를 할 때 실패하는 일이 그리 많지는 않을 것이다.
다만 개발 초기도 아니고, 본인이 혼자 개발하는 것도 아니라면 테스트할 때 실패하는 케이스가 있을 것이다.
간혹 데이터를 무조건 생성하는 부분이 문제가 될 수 있겠지만
보통은 기존에 저장된 데이터가 존재해서 발생한다.
만약 내가 테이블에 A, B, C를 저장해서 데이터를 전체 조회해서 그 결과가 A, B, C인지 확인하는 테스트를 작성한다고 가정해보자.
내가 테이블을 혼자 사용하거나 방금 만든 테이블이면 상관없겠지만
누군가 기존에 D, E를 저장했다면 데이터를 전체 조회했을 때 A, B, C, D, E가 나와서 테스트는 실패할 것이다.
이처럼 테스트를 할 때는 기존 DB를 사용하면 문제가 생긴다.
이럴 때는 테스트용으로 DB 자체를 분리하자.
테스트 - 데이터 롤백
테스트할 때는 매우 중요한 2가지 원칙이 있다.
- 테스트는 다른 테스트와 격리해야 한다.
- 테스트는 반복해서 실행할 수 있어야 한다.
이 것을 아까의 상황에 대입해보자.
사실 2건 다 동시에 적용되기는 하는데
나는 3건을 등록했는데 기존 데이터때문에 실제 조회되는 데이터는 5건이라면 매우 곤란하다.
그럴 때는 트랜잭션을 활용해서 데이터를 롤백시키면 된다.
실제로 트랜잭션의 원리를 생각해보면 커밋을 하지 않는다면 현재 세션에만 데이터가 존재하기 때문에
다른 세션에서는 데이터가 보이지 않아서 해당 세션의 테스트에는 영향을 끼치지 않으며,
또한 롤백이 되서 해당 데이터는 사라지기 때문에 다시 테스트를 진행할 수 있다.
@BeforeEach와 @AfterEach
스프링은 클래스 내부의 메소드를 실행하기 전과 후에 항상 동작하는 메소드를 만들 수 있는
@BeforeEach
와 @AfterEach
라는 애노테이션을 지원한다.
사용법은 간단하다.
우선은 PlatformTransactionManager
와 TransactionStatus
을 선언해주자.
//트랜잭션 관련 코드
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status;
그런 다음에 @BeforeEach
애노테이션을 통해서 트랜잭션을 시작하는 메소드를 만들어주자.
@BeforeEach
void beforeEach() {
//트랜잭션 시작
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
마지막으로 @AfterEach
애노테이션을 통해서 롤백해주는 메소드를 만들면 끝이다.
@AfterEach
void afterEach() {
//트랜잭션 롤백
transactionManager.rollback(status);
}
테스트 - @Transactional
그런데 테스트마다 @BeforeEach
와 @AfterEach
으로 메소드를 만드는 것은 매우 귀찮다.
그래서 스프링은 @Transactional
애노테이션을 통해 매우 간단하게 해결해준다.
그저 테스트 클래스에 @Transactional
애노테이션을 추가해주면 적용 끝이다!
그러면 테스트 메소드가 실행될 때 자동으로 트랜잭션을 시작하고, 테스트가 종료되면 자동으로 롤백시켜준다.
정말 커밋하고 싶다면?
아주 가끔 테스트할 때도 DB에 저장하고 싶을 때가 있다.
그럴 때는 테스트할 클래스나 메소드에 @Commit
이나 @Rollback(value = false)
를 붙이면 된다.
테스트 - 임베디드 모드 DB
테스트용 DB를 설치하고 운영하기 번거로울 때는 임베디드 모드
를 활용하면 된다.
H2
같은 DB는 자바로 개발되었고, JVM 안에서 메모리 모드로 동작하는 특별한 기능을 제공한다.
그래서 애플리케이션을 실행할 때 H2
를 해당 JVM 메모리에 포함해서 함께 실행할 수 있다.
이렇게 DB를 해플리케이션에 내장해서 함께 실행하는 경우를 임베디드 모드
라고 부른다.
애플리케이션이 종료되면 임베디드 모드로 동작하는 데이터를 포함해서 DB 자체가 사라진다.
임베디드 모드
를 사용할 때는 아래처럼 DataSource
를 빈을 테스트용으로 등록하면 된다.
@Bean
@Profile("test")
public DataSource dataSource() {
log.info("메모리 데이터베이스 초기화");
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("org.h2.Driver");
dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
dataSource.setUsername("sa");
dataSource.setPassword("");
return dataSource;
}
jdbc:h2:mem:db;
라고 명시해야지 임베디드 모드
를 사용할 수 있다.
그리고 임베디드 모드
에서는 DB 커넥션 연결이 모두 끊어지면 DB도 함께 종료되어 버린다.
이를 방지하기 위해 옵션으로 DB_CLOSE_DELAY=-1
을 명시해주자.
테이블 및 데이터 생성
DB가 사라진다는 것은 항상 테이블을 새로 만들어줘야 한다는 뜻이다.
src/test/resources
폴더에 schema.sql
라는 이름으로 SQL 파일을 만들어주자.
이제 해당 SQL 파일에서 테스트에 필요한 테이블을 만드는 쿼리들을 작성해주자.
파일 이름은 항상 schema.sql
이어야 하는 것을 주의하자.
공식 문서
테스트 - 스프링 부트와 임베디드 모드
사실 스프링에서는 앞선 빈 등록을 안 해도 임베디드 모드를 사용할 수 있게 기능을 제공해준다.
src/test/resources
폴더에 있는 application.properties
의 DB 정보도 모두 주석처리하고,
아까 만들었던 테스트용 DataSource
빈도 주석처리하자.
그러면 스프링이 알아서 메모리 DB로 테스트를 실행해준다.
그리고 로그를 보면 스프링이 여러 데이터 소스를 사용하는데 같은 DB를 사용해서 충돌이 나는 상황을 방지하기 위해
임의의 이름으로 로그를 출력해주는 것을 알 수 있다.
만약 고정된 이름으로 보고 싶다면 application.properties
에
spring.datasource.generate-unique-name=false
를 명시해주면 된다.
또한 스프링 부트 3.x대 부터는 org.springframework.test.context.transaction.TransactionContext
에서 남기는 로그 레벨이
INFO
에서 TRACE
로 변경되었다.
그러니 자세한 로그를 확인하고 싶다면 application.properties
에
logging.level.org.springframework.test.context.transaction=trace
를 명시해주면 된다.
물론 핵심적인 부분만 보고 싶다면 DEBUG
로만 명시해도 된다.
그리고 개발인지 테스트인지 확인해서 어느 쪽 application.properties
인지 확인하고 값을 명시하자.