Spring에서 Database Test 해보기

MinSeong Kang·2022년 7월 19일
0

spring

목록 보기
8/18

데이터베이스와 연동해서 데이터베이스 관한 테스트를 할 때, 실제 데이터베이스에 접근하여 데이터가 잘 저장되고 조회가능한지 확인하는 과정이 필요하다.

테스트 데이터베이스 연동

테스트 케이스는 src/test에서 실행되기 때문에 src/test에 있는 application.properties에 기존 데이터베이스 설정을 해야한다.

설정 이후 테스트 케이스를 실행시켰을 때, 모든 테스트 케이스가 성공적으로 테스트가 될 수 있지만 실패가 될 가능성이 높다.
왜냐하면 기존 데이터 베이스에 이미 과거에 저장했던 데이터가 보관되어 있다면, 과거 데이터는 해당 테스트에 영향을 주기 때문이다.

따라서 실제 서비스에서 사용하는 데이터베이스와 테스트용 데이터베이스를 별도로 운영해야한다.


데이터 베이스의 분리

테스트용 데이터베이스를 별도로 생성하고 테스트에 필요한 테이블을 만든다. 이후 테스트용 데이터베이스에 대한 정보를 src/test에 있는 application.properties에 설정한다.

테스트용 데이터베이스를 통해 테스트 케이스를 실행시켰을 경우도, 안타깝게도 테스트 케이스는 성공할 수도 있고 실패할 수도 있다. 왜냐하면 테스트 케이스가 여러 개 실행되는 경우 각 테스트 케이스를 테스트할 때마다 데이터가 저장되어 테스트용 데이터베이스에 남기 때문에 저장된 데이터들은 나머지 테스트 케이스에 영향을 준다.

따라서 테스트 케이스를 실행시킬 때마다 추가한 데이터를 삭제하는 작업이 필요하다. 이는 테스트가 끝날 때마다 DELETE SQL를 통해 해결할 수 있겠지만, 테스트가 실행되는 도중에 예외가 발생하거나 애플리케이션이 종료되어 테스트 종료 시점에 DELETE SQL을 호출하지 못할 가능성이 있어 좋은 해결책은 아니다.

트랜잭션과 롤백전략을 사용하여 각 테스트마다 저장되는 데이터를 깔끔하게 제거하여 각 테스트를 독립적으로 실행시킬 수 있다.


트랜잭션과 롤백전략 사용

각 트랜잭션이 끝나고 트랜잭션을 강제로 롤백시키면 추가된 데이터를 깔끔하게 삭제할 수 있다. 또한 중간에 오류가 발생하여 롤백을 호출하지 못하더라도 트랜잭션을 커밋하지 않았기 때문에 데이터베이스에 해당 데이터는 반영되지 않는다.

테스트에 트랜잭션과 롤백을 적용하기 위해 코드를 추가한다.

@Autowired
PlatformTransactionManager transactionManager;

TransactionStatus status;

@BeforeEach
void beforeEach() {
	// 트랜잭션 시작
    status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}

@AfterEach
void afterEach() {
	// 트랜잭션 롤백
    transactionManager.rollback(status);
}

스프링 부트는 자동으로 적절한 트랜잭션 매니저를 스프링 빈에 등록해주며, @BeforeEach@AfterEach를 통해 각 테스트 케이스를 실행시키기 직전에 트랜잭션을 시작시켜 각각의 테스트가 트랜잭션 범위 안에서 실행될 수 있도록하고, 테스트 케이스 완료 후 트랜잭션을 롤백시킬 수 있다.

별도의 테스트용 데이터베이스를 만들고 필요한 테이블을 생성한 후 트랜잭션과 롤백전략을 사용하여, 각각의 테이스 케이스가 독립적으로 실행시킬 수 있도록 하였다.

스프링은 위의 코드 추가없이 테스트에 트랜잭션을 적용하고 롤백하는 방식을 @Transcation 어노테이션 하나로 깔끔하게 해결해준다.

@Transactional
@SpringBootTest
class ItemRepositoryTest {
	...
}

원래 스프링에서 제공하는 @Transaction 어노테이션은 트랜잭션 내 로직이 성공적으로 수행되면 커밋되도록 동작된다. 하지만 @Transaction 어노테이션이 테스트에 있으면 각각의 테스트를 트랜잭션 안에서 실행하게 하고, 테스트가 끝나면 트랜잭션을 자동으로 롤백시킨다.

만일 테스트 도중 실제 데이터베이스에 어떻게 데이터가 저장되는지 확인해보고 싶은 경우 @Commit or @Rollback(value = false) 어노테이션을 통해 강제로 롤백대신 커밋을 호출할 수 있다.

@Transaction 어노테이션을 하나로 모든 테스트 케이스를 트랜잭션 내에서 실행시키고, 종료 이후 롤백시켜 각 테스트 케이스가 서로 영향받지 않게 실행될 수 잇다.


임베디드 모드 DB

하지만 별도의 테스트용 데이터베이스를 만드는 것도 상당히 번거로운 일이다. 테스트를 검증하고 테스트용 데이터베이스 자체는 삭제되어도 된다.

H2 데이터베이스는 자바로 개발되어 있고, JVM 안에서 메모리 모드로 동작하는 기능을 제공한다. 따라서 H2 데이터베이스를 애플리케이션에 내장해서 함께 실행시키는 것이 가능하다. 한마디로 테스트용 데이터베이스를 만들지 않고, 임베디드 모드로 동작하는 H2 데이터베이스를 통해 데이터베이스 접근 테스트가 가능하다.

@Bean
@Profile("test")
public DataSource dataSource() {
	DriverManagerDataSource dataSource = new DriverManagerDataSource();
	dataSource.setDriverClassName("org.h2.Driver");
	dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
	dataSource.setUsername("min");
	dataSource.setPassword("");
	return dataSource;
}

위의 코드를 XXXApplication.java에 추가한다. Profile이 test인 경우 해당 datasource를 스프링 빈에 등록하기 때문에 src/test application.properties 에는 profile를 test로 설정하는 코드를 필요하다.

spring.profiles.active=test

jdbc:h2:mem 를 통해 임베디드 모드로 동작하는 데이터 베이스를 사용할 수 있다.

이 후 src/test/resources/shema.sql 에 테이블을 생성하는 sql를 작성해야한다. 아래 코드는 예시이다.

drop table if exists item CASCADE;
create table item (
    id bigint generated by default as identity,
    item_name varchar(10),
    price integer,
    quantity integer,
    primary key (id)
);

이후 실제 데이터베이스 서버를 끈 상태로 테스트를 진행하더라도 임베디드 모드 h2 데이터베이스가 실행되어 테스트가 잘 실행될 것이다.

임베디드 모드 h2 데이터베이스를 사용하여 테스트용 데이터베이스를 만들지 않고도 데이터베이스 접근 테스트를 실행할 수 있다.

하지만 이러한 작업도 스프링이 자동으로 설정해준다.

  • 임베디드 모드 h2 데이터베이스를 사용하기 위해 datasource를 Bean에 등록한 코드
  • src/test에 application.properties에 데이터베이스에 접근하기 위한 설정 정보

두 코드를 모두 지우고 테스트 케이스를 실행시켜보아도 테스트는 잘 실행될 것이다.

스프링에서 임베디드 모드를 지원하는 데이터베이스를 사용할 경우, 테스트 코드에 @Transaction 어노테이션만 사용한다면, 지금까지 설명한 모든 과정을 해결할 수 있다.
스프링에서 임베디드 모드를 지원하는 데이터 베이스 : H2, HSQL, Derby


참고 문헌

https://docs.spring.io/spring-boot/docs/current/reference/html/data.html#data.sql.datasource.embedded
https://www.inflearn.com/course/스프링-db-2/

0개의 댓글