실제 개발 코드가 아니라 테스트를 실행할때 실제 데이터베이스를 연동하기 위해서는 어떻게해야할까?
실제 운영코드와 마찬가지로 src/test/resources/application.properties 파일을 생성해줘야한다.
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa
#jdbcTemplate sql log
logging.level.org.springframework.jdbc=debug
테스트케이스는 src/test에 있기 때문에 실행하면 src/test에 있는 application.properties 파일이 우선순위를 가지고 실행된다. 만약 없다면 운영에 존재하는 application.properties를 사용하게 된다.
테스트코드를 보면 @SpringBootTest 애노테이션을 확인할 수 있다.
이 애노테이션은 상위 패키지에서 @SpringBootApplication을 찾아서 테스트 코드가 동일한 설정을 갖을 수 있도록 지원한다.
따라서, 실제 운영코드인 아래의 코드를 테스트할때도 동일한 설정으로 가져간다.
@Import(V2Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
로컬과 테스트에서는 서로 다른 데이터베이스를 사용하는게 좋다. 즉 테스트를 다른 환경과 철저하게 분리해야한다. 이를 위한 가장 간단한 방법은 테스트 전용 데이터베이스를 별도로 운영하는것이다.
또한, 테스트에서는 아래의 2가지 매우 중요한 원칙을 갖는다.
만약 어떤 테스트 케이스에서 추가한 데이터가 다른 테스트에서 영향을 받아서 테스트가 실패하면 어떨까?
위에서 언급한 테스트는 다른 테스트와 격리해야한다를 지키지못한다.
이를 지키기 위해서 기존 데이터를 수동으로 지울수있지만 이것보다 더 나은 방법이 있다.
그건 바로 트랜잭션과 롤백 전략이다.
테스트가 끝나고 나서 트랜잭션을 강제로 롤백해버리면 데이터가 깔끔하게 제거된다.
테스트를 하면서 데이터를 이미 저장했는데, 중간에 테스트가 실패해서 롤백을 호출하지 못해도 괜찮다.
트랜잭션을 커밋하지 않았기 때문에 데이터베이스에 해당 데이터가 반영되지 않는다.
이렇게 트랜잭션을 활용하면 테스트가 끝나고 나서 데이터를 깔끔하게 원래 상태로 되돌릴 수 있다.
테스트는 각각의 테스트 실행 전 후로 동작하는 @BeforeEach, @AfterEach라는 편리한 기능을 제공한다.
스프링은 테스트 데이터 초기화를 위해 트랜잭션을 적용하고 롤백하는 방식을 @Transactional 애노테이션 하나로 깔끔하게 해결해준다.
아래는 테스트 예시 코드이다.
@Slf4j
@Transactional
@SpringBootTest
class ItemRepositoryTest {
@Transactional은 import org.springframework.transaction.annotation.Transactional를 사용한다.
일반적으로 @Transactional 애노테이션은 로직이 성공적으로 수행되면 커밋하도록 동작한다. 그런데 @Transactional을 테스트에서 사용하면 아주 특별하게 동작한다. @Transactional이 테스트에서 적용되면 스프링은 테스트를 트랜잭션 안에서 실행시키고 테스트가 끝나면 트랜잭션을 자동으로 롤백시켜버린다.
@Transactional 을 테스트에서 사용하면 테스트가 끝나면 바로 롤백되기 때문에 테스트 과정에서 저장한 모든 데이터가 사라진다. 당연히 이렇게 되어야 하지만, 정말 가끔은 데이터베이스에 데이터가 잘 보관되었는지 최종 결과를 눈으로 확인하고 싶을 때도 있다. 이럴 때는 다음과 같이 @Commit 을 클래스 또는 메서드에 붙이면 테스트 종료후 롤백 대신 커밋이 호출된다. 참고로 @Rollback(value = false) 를사용해도 된다.
@Commit
@Transactional
@SpringBootTest
class ItemRepositoryTest {}
H2 데이터베이스는 자바로 개발되어 있고, JVM안에서 메모리 모드로 동작하는 특별한 기능을 제공한다. 그래서 애플리케이션을 실행할 때 H2 데이터베이스도 해당 JVM 메모리에 포함해서 함께 실행할 수 있다. DB를 애플리케이션에 내장해서 함께 실행한다고 해서 임베디드 모드(Embedded mode)라 한다. 물론 애플리케이션이 종료되면 임베디드 모드로 동작하는 H2 데이터베이스도 함께 종료되고, 데이터도 모두 사라진다. 쉽게 이야기해서 애플리케이션에서 자바 메모리를 함께 사용하는 라이브러리처럼 동작하는 것이다
@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;
}
메모리 DB는 애플리케이션이 종료될 때 함께 사라지기 때문에, 애플리케이션 실행 시점에 데이터베이스 테이블도 새로 만들어주어야 한다.
JDBC나 JdbcTemplate를 직접 사용해서 테이블을 생성하는 DDL을 호출해도 되지만, 너무 불편하다.
스프링 부트는 SQL 스크립트를 실행해서 애플리케이션 로딩 시점에 데이터베이스를 초기화하는 기능을
제공한다.
src/test/resources/schema.sql 파일을 생성해주고 그 파일안에 DDL 관련 내용을 채워주면 스프링이 테스트시에 schema.sql을 실행시켜준다.
src/test/resources/data.sql 파일에는 DDL이 아닌 DML 쿼리를 추가해줘서 자동으로 실행되도록 할 수도 있다.
스프링부트는 임베디드 데이터베이스에 대한 설정도 기본적으로 제공한다.
스프링 부트는 데이터베이스에 대한 별다른 설정이 없으면 임베디드 데이터베이스를 사용한다. 즉, application.properties에 datasource 설정이 없다면 알아서 메모리 데이터베이스로 실행된다.
해당 포스팅은 아래의 강의를 공부하여 정리한 내용입니다.
김영한님의 SpringDB2-데이터베이스연동테스트