Spring-Boot/스프링 DB 2편 - 데이터 접근 기술

스프링 DB 데이터접근 기술 - 테스트

PHM 2022. 9. 14. 18:41

* @SpringBootTest *

- @SpringBootTest는 @SpringBootApplication 를 찾아서 설정으로 사용한다.

 

테스트의 원칙

- * 테스트는 다른 테스트와 격리해야 한다. *

- * 테스트는 반복해서 실행할 수 있어야 한다. *

 

 

* 트랜잭션과 롤백 전략 *

- 테스트가 끝나고 나서 트랜잭션을 강제로 롤백해버리면 데이터가 깔끔하게 제거된다.

 

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

- 트랜잭션 관리자는 `PlatformTransactionManager`를 주입 받아서 사용

    ㆍ스프링 부트가 자동으로 적절한 트랜잭션 매니저를 스프링 빈으로 등록

- @BeforeEach : 각각의 테스트 케이스를 실행하기 직전에 호출된다. 

    ㆍ따라서 여기서 트랜잭션을 시작하면 된다.

    ㆍ`transactionManager.getTransaction(new DefaultTransactionDefinition()) 로 트랜잭션을 시작한다.

- @AfterEach : 각각의 테스트 케이스가 완료된 직후에 호출

    ㆍ따라서 여기서 트랜잭션을 롤백하면 된다.

    ㆍ`transactionManager.rollback(status)`로 트랜잭션을 롤백

 

@Transactional 원리

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

- 그런데 @Transactional 은 테스트에서 사용하면 아주 특별하게 동작

    ㆍ테스트를 트랜잭션안에서 실행하고, 테스트가 끝나면 트랜잭션을 자동으로 롤백

 

* 정리 *

- 테스트가 끝난 후 개발자가 직접 데이터를 삭제하지 않아도 되는 편리함을 제공

- 테스트 실행 중에 데이터를 등록하고 중간에 테스트가 강제로 종료되어도 걱정 없다.

  이 경우 트랜잭션을 커밋하지 않기 때문에, 데이터는 자동으로 롤백된다.

   (보통 데이터베이스 커넥션이 끊어지면 자동으로 롤백되어 버린다.)

- 트랜잭션 범위 안에서 테스트를 진행하기 때문에 동시에 다른 테스트가 진행되어도 서로 영향을 주지 않는 장점이 있다

- @Transactional 덕분에 아주 편리하게 다음 원칙을 지킬 수 있다.

    ㆍ테스트는 다른 테스트와 격리해야 한다

    ㆍ테스트는 반보해서 실행할 수 있어야 한다

 

* 강제로 커밋하기 - @Commit *

- @Transactional 을 테스트에서 사용하면 테스트가 끝나면 바로 롤백되기 때문에 테스트 과정에서 저장한 모든 데이터가 사라진다.

- 데이터베이스에 데이터가 잘 보관되었는지 확인하고 싶을 때 @Commit을 클래스 또는 메서드에 붙이면 테스트 종료 후 롤백 대신 커밋이 호출 된다.

 


* 임베디드 모드 *

- H2 데이터베이스는 자바로 개발되어 있고, JVM안에서 메모리 모드로 동작하는 특별한 기능을 제공한다.

- 그래서 애플리케이션을 실행할 때 H2데이터베이스도 해당 JVM메모리에 포함해서 함께 실행할 수 있다.

- DB를 애플리케이션에 내장해서 함께 실행한다고 해서 임베디드 모드(Embeded 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;
}

- dataSource()

    ㆍ`jdbc:h2:mem:db` : 이 부분이 중요하다. 데이터소스를 만들때 이렇게 적으면 임베디드 모드(메모리 모드)로

                                         동작하는 H2 데이터베이스를 사용할 수 있다

    ㆍ`DB_CLOSE_DELAY=-1` : 임베디드모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도

                                                 종료되는데, 그것을 방지하는 설정이다

    ㆍ이 데이터소스를 사용하면 메모리 DB를 사용할 수 있다. 

 

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

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

- JDBC나 JdbcTemplate를 직접 사용해서 테이블을 생성하는 DDL을 호출해도 되지만, 너무 불편하다

- 스프링 부트는 SQL 스크립트를 실행해서 애플리케이션 로딩 시점에 데이터베이스 초기화하는 기능을 제공한다

 

- 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)
);

스프링 부트와 임베디드 모드

- test properties의 DB 정보 삭제

- 이렇게 별다른 정보가 없으면 스프링 부트는 임베디드 모드로 접근하는 데이터소스(`DataSource`)를 만들어서 제공한다. 

 

- 참고로 로그를 보면 `jdbc:h2:mem` 뒤에 임의의 데이터베이스 이름이 들어가 있다.

- 이것은 혹시라도 여러 데이터소스가 사용될 때 같은 데이터베이스를 사용하면서 발생하는 충돌을 방지하기 위한 스프링 부트가 임의의 이름을 부여한 것이다.