[Spring DB] 테스트용 DB 분리하기 및 설정

Loopy·2023년 1월 28일
0

스프링

목록 보기
12/16
post-thumbnail

☁️ 테스트용 DB 분리

만약 로컬에서 사용하는 어플리케이션 서버와 테스트에서 같은 데이터베이스를 사용하고 있다 가정해보자.테스트용 데이터들은 다음 테스트의 성공을 위해 끝날 때 초기화 되어 있어야 하지만, 어플리케이션 서버에서는 초기값이 필요한 경우라면? 둘이서 같은 데이터베이스를 사용하면 안된다.

가장 간단한 방법은, 다음과 같이테스트 전용 데이터베이스를 따로 생성하면 된다.

jdbc:h2:~/testcase

혹은, 테스트가 끝날 때 마다 데이터를 삭제해주면 된다.

🔖 좋은 테스트의 두 가지 조건
테스트는 다른 테스트와 격리해야 한다.
테스트는 반복해서 실행할 수 있어야 한다.

하지만 테스트 끝날 때마다 delete sql 을 통해 데이터를 삭제하는 것은 올바른 방법이 아니다. 테스트가 실행되는 도중에 예외가 발생하거나 애플리케이션이 종료되어 버려서 테스트 종료 시점에 DELETE SQL 을 호출하지 못할 수도 있다! 그러면 결국 데이터가 남아있게 된다.

☁️ 트랜잭션과 롤백 전략

트랜잭션을 이용해서, 테스트 후 강제 롤백해버리면 데이터를 테스트 실행 이전 상태로 복구할 수 있다. 이런 경우 예외가 발생해서 롤백을 처리하지 못하더라도, 애초에 커밋된 것이 아닌 임시 저장된 상태므로 괜찮아진다.

  1. 트랜잭션 시작
  2. 테스트 A 실행
  3. 트랜잭션 롤백
@SpringBootTest 
class ItemRepositoryTest {

    @Autowired
    PlatformTransactionManager transactionManager;
    TransactionStatus status;

    @BeforeEach
    void before() {
        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }

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

@BeforeEach 을 통해 트랜잭션을 시작함으로써, 각각의 테스트가 트랜잭션 범위 안에서 동작할 수 있게 된다. (트랜잭션 동기화 매니저에서 같은 커넥션을 가져다 쓰므로)

@AfterEach 를 통해 각각의 테스트가 끝난 이후에 데이터를 롤백해주면 데이터가 남지 않아 다음 테스트에 영향을 주지 않는다. 따라서 여러번 반복해서 테스트를 해도 성공한다.

☁️ @Transactional

스프링은 테스트 데이터 초기화를 이용해 트랜잭션을 적용하고 롤백하는 과정을 @Transactional 어노테이션 하나로 해결해준다.

동작 방식

테스트 케이스의 메서드나 클래스에 Transactional을 직접 붙여 사용할때만, 로직 수행 성공시 커밋이 아닌 롤백을 해버리는 방식으로 동작한다.

따라서 만약 서비스나 레포지토리에 @Transactional이 붙어 있어도 테스트에서 시작한 트랜잭션이 전파되어 테스트 함수 내부의 모든 로직은 같은 커넥션을 사용하게 된다.(더 자세한 내용은 트랜잭션 전파 파트에서)

강제 커밋

@Commit 이나 @Rollback(value = false)를 붙이면 커밋이 되어 데이터를 확인할 수 있다.

@Commit
@Transactional
@SpringBootTest
class ItemRepositoryTest {}

☁️ 임베디드 모드 DB

테스트 케이스를 실행하기 위해서 별도의 데이터베이스를 설치하고, 운영하는 것은 상당히 번잡한 작업이다. 단순히 테스트를 검증할 용도로만 사용하기 때문에, 테스트가 끝나면 데이터베이스의 데이터를 모두 삭제하거나 데이터베이스를 제거해도 된다.

임베디드 모드 DB 개념

데이터페이스를 애플리케이션에 내장에서 함께 실행한다고 하여 임베디도 모드라고 부른다. 가장 대표적인 H2 데이터베이스는 자바로 개발되어 있고, JVM안에서 메모리 모드로 동작하는 특별한 기능을 제공한다. 따라서 애플리케이션 종료 시 데이터 뿐만 아니라 데이터베이스 자체가 삭제된다.

쉽게 말해서 애플리케이션 자바 메모리를 함께 사용하는 라이브러리 처럼 동작한다.

@Slf4j
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")  // controller만 컴포넌트 스캔
public class ItemServiceApplication {

	public static void main(String[] args) {
		SpringApplication.run(ItemServiceApplication.class, args);
	}
    ...

	@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;
	}

SQL Script

메모리 DB는 어플리케이션이 종료되면 사라지기 때문에, 매번 테이블들을 생성해주어야 한다. 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)
    );

☁️ SpringBoot와 임베디드 모드

데이터베이스에 접근하는 별다른 설정 정보가 주어지지 않는다면, 스프링 부트는 기본적으로 임베디드 모드로 접근하는 DataSource 를 자동으로 만들어준다. 임베디드를 위한 설정을 하지 않아도 되어 매우 편리하다.

정리
1. @Transactional 하나만 있으면 반복 테스트 및 테스트 격리 가능
2. 별다른 설정이 없다면 메모리 기반으로 동작하는 DataSource 가 생성

profile
개인용으로 공부하는 공간입니다. 잘못된 부분은 피드백 부탁드립니다!

0개의 댓글