본문 바로가기
Spring Study/DataBase

[DataBase] 데이터 접근 기술 - 테스트

by 정재인 2023. 9. 25.

테스트 - 데이터베이스 분리

로컬에서 사용하는 애플리케이션 서버와 테스트에서 같은 데이터베이스를 사용하면 테스트에서 문제가 발생한다.

이를 해결하기 웨해 테스트를 다른 환경과 철저하게 분리해야 한다.

가장 간단한 방법으로는 테스트 전용 데이터베이스를 별도로 운영하는 것이다.

 

테스트에서 매우 중요한 원칙

1. 테스트는 다른 테스트와 격리해야 한다.
2. 테스트는 반복해서 실행할 수 있어야 한다.

 

테스트가 끝날 때마다 추가한 데이터에 DELETE SQL을 사용해도 되지만, 가장 좋은 방법은 트랜잭션, 롤백이다.

 

 


테스트 - 데이터 롤백

테스트가 끝나고 트랜잭션을 강제로 롤백해버리면 데이터가 제거된다. 테스트를 하면서 데이터를 이미 저장했는데, 중간에 테스트가 실패해 롤백을 호출하지 못해도 트랜잭션을 커밋하지 않았기 때문에 데이터가 반영되지 않는다.

 

실행순서

1. 트랜잭션 시작
2. 테스트 A 실행
3. 트랜잭션 롤백

4. 트랜잭션 시작
5. 테스트 B 실행
6. 트랜잭션 롤백

 

테스트는 각각의 테스트 실행 전 후로 동작하는 @BeforeEach, @AfterEach라는 기능을 제공한다.

@SpringBootTest
class ItemRepositoryTest {
    
    @Autowired
    ItemRepository itemRepository;

    //트랜잭션 관련 코드
    @Autowired
    PlatformTransactionManager transactionManager;
    TransactionStatus status;
    
    @BeforeEach
    void beforeEach() {
        //트랜잭션 시작
        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }
    
    @AfterEach
    void afterEach() {
    //MemoryItemRepository의 경우 제한적으로 사용
    if (itemRepository instanceof MemoryItemRepository) {
        ((MemoryItemRepository) itemRepository).clearStore();
    }

    //트랜잭션 롤백
    transactionManager.rollback(status);
  }
//...
}

 

- @BeforeEach: 각 테스트 케이스를 실행하기 직전에 호출된다. 따라서 여기서 트랜잭션을 시작하면 된다.

- @AfterEach: 각각의 테스트 케이스가 완료된 직후에 호출된다. 따라서 여기서 트랜잭션을 롤백하면 된다.

 

 


테스트 - @Transactional

import org.springframework.transaction.annotation.Transactional;

@Transactional
@SpringBootTest
class ItemRepositoryTest {
    
    //트랜잭션 관련 코드
/*
    @Autowired
    ItemRepository itemRepository;

    //트랜잭션 관련 코드
    @Autowired
    PlatformTransactionManager transactionManager;
    TransactionStatus status;
    
    @BeforeEach
    void beforeEach() {
        //트랜잭션 시작
        status = transactionManager.getTransaction(new DefaultTransactionDefinition());
    }
*/
    
    @AfterEach
    void afterEach() {
    //MemoryItemRepository의 경우 제한적으로 사용
    if (itemRepository instanceof MemoryItemRepository) {
        ((MemoryItemRepository) itemRepository).clearStore();
    }

    //트랜잭션 롤백
    //transactionManager.rollback(status);
  }
//...
}

 

@Transactional 원리

스프링이 제공하는 @Transactional 애노테이션은 로직이 성공적으로 수행되면 commit하도록 동작한다.

하지만 테스트에서 사용하면 스프링은 테스트를 트랜잭션 안에서 실행하고, 테스트가 끝나면 트랜잭션을 자동으로 rollback시킨다.

 

@Transactional 테스트 동작 방식

 

강제로 커밋하기 - @Commit

@Transactional을 테스트에서 사용하면 테스트가 끝나면 바로 롤백되기 때문에 테스트 과정에서 저장한 모든 데이터가 사라진다. 하지만 가끔은 데이터베이스에 데이터가 잘 저장되어있는지 확인하고 싶을 때도 있다.

이럴 때 @Commit을 클래스 또는 메서드에 붙이면 테스트 종료 후 롤백 대신 커밋이 호출된다. 

@Rollback(value = false)를 사용해도 된다.

 

import org.springframework.test.annotation.Commit;
@Commit
@Rollback(value = false)
@Transactional
@SpringBootTest
class ItemRepositoryTest {}

 


테스트 - 임베디드 모드 DB

DB를 애플리케이션에 내장해서 함께 실행한다고 해서 임베디드 모드라고 한다. 

즉, 애플리케이션에서 자바 메모리를 함께 사용하는 라이브러리처럼 동작하는 것이다.

 

package hello.itemservice;

@Slf4j
//@Import(MemoryConfig.class)
//@Import(JdbcTemplateV1Config.class)
//@Import(JdbcTemplateV2Config.class)
@Import(JdbcTemplateV3Config.class)
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")
public class ItemServiceApplication {

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

    @Bean
    @Profile("local")
    public TestDataInit testDataInit(ItemRepository itemRepository) {
        return new TestDataInit(itemRepository);
    }
    @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;
    }
}

@Profile("test")

프로필이 test인 경우에만 데이터소스를 스프링 빈으로 등록한다.

테스트 케이스에서만 이 데이터 소스를 스프링 빈으로 등록해서 사용하겠다는 뜻이다.

 

jdbc:h2:mem:db

데이터소스를 만들때 이렇게만 적으면 임베디드 모드(메모리 모드)로 동작하는 H2 데이터베이스를 사용할 수 있다.

 

스프링 부트 - 기본 SQL 스크립트를 사용해서 데이터베이스를 초기화하는 기능

메모리 DB는 애플리케이션이 종료될 때 함께 사라지기 때문에, 애플리케이션 실행 시점에 데이터베이스 테이블도 새로 만들어주어야 한다.

 

src/test/resources/schema.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)
    );

 

'Spring Study > DataBase' 카테고리의 다른 글

[DataBase] 스프링 트랜잭션 이해  (1) 2023.10.06
[DataBase] JPA / Spring Data JPA  (0) 2023.10.03
[DataBase] 데이터 접근 기술  (0) 2023.09.22
[DataBase] 예외 처리  (0) 2023.09.21
[DataBase] 자바 예외 (Exception)  (0) 2023.09.20

댓글