메모리는 쓰레드를 종료시키면 가지고 있던 데이터가 같이 없어지게 되지만 DB의 경우 그렇지 않습니다.
테스트에 사용된 데이터는 DB에 저장하지않고 테스트 실행 직전으로 돌려야합니다.
이런 상황에서 테스트 하는 방법을 알아보겠습니다.
@SpringBootTest 애노테이션이 붙은 클래스는 @SpringBootApplication 을 가진 클래스의 설정 정보를 사용합니다.
테스트 - 데이터베이스 분리
H2 DB 기준 새로운 DB 생성 방법
- 기존 실행 중이던 DB를 종료 후 다시 실행
- ip부분을 -> localhost 로 수정 후
- JDBC URL에 jdbc:h2:~/[DB명] 을 입력하면
- ~/[DB명].mv.db 파일 생성 확인
- 이후는 tcp를 이용해 원격 접속 ( jdbc:h2:tcp://localhost/~/[DB명]
분리하더라도 기존 데이터가 남아있어 첫 테스트를 제외한 이후 테스트는 실패하는 코드가 생긴다.
테스트 중요한 원칙
- 테스트는 다른 테스트와 격리해야한다.
- 테스트는 반복해서 실행할 수 있어야한다.
를 어기게 된다.
DELETE 나 DROP TABLE 후 테이블 생성을 하는 방법은 옳지 못하다. 중간에 예외가 발생하면 데이터찌꺼기는 남게 된다.
가장 좋은 방법으로는 롤백이 있습니다.
- 트랜잭션 시작
- 테스트 A 실행
- 트랜잭션 롤백
- 트랜잭션 시작
- 테스트 B 실행
- 트랜잭션 롤백
테스트 - 트랜잭션 시작과 롤백
@SpringBootTest
class TestClass {
...
@Autowired
CustomRepository repository;
//TransactionManager와 DataSource는 적절한 구현체를 Spring에서 자동으로 DI
@Autowired
PlatformTransactionManager transactionManager;
TransactionStatus status; //beforeEach와 afterEach에서 사용하기 위한 전역변수 선언
@BeforeEach
void beforeEach() {
status = transactionManager.getTransaction(new DefaultTransactionDefinition());
}
@AfterEach
void afterEach() {
transactionManager.rollback(status);
}
...
}
위와같이 BeforeEach와 AfterEach를 통해 공통코드를 분리해서 트랜잭션 시작과 롤백을 할 수 있습니다.
테스트 - @Transactional
@Transactional 애노테이션은 일반적인 상황에서는 프록시 AOP기능으로써
성공하면 커밋, 실패하면 롤백을 하지만 테스트에서 해당 애너테이션을 붙이게되면
트랜잭션 시작과 롤백을 하나의 애노테이션으로 해결할 수 있게 해줍니다.
- 테스트에 @Transactional 애노테이션이 테스트 메서드나 클래스에 있으면 먼저 트랜잭션을 시작한다.
- 테스트 로직을 실행한다. 테스트가 끝날 때 까지 모든 로직은 트랜잭션 안에서 수행된다.
- 트랜잭션은 기본적으로 전파되기 때문에, 리포지토리에서 사용하는 JdbcTemplate도 같은 트랜잭션을 사용한다.
- 테스트 실행 중에 INSERT SQL을 사용해서 item1 , item2 , item3 를 데이터베이스에 저장한다.
- 물론 테스트가 리포지토리를 호출하고, 리포지토리는 JdbcTemplate을 사용해서 데이터를 저장한다.
- 검증을 위해서 SELECT SQL로 데이터를 조회한다. 여기서는 앞서 저장한 item1 , item2 , item3 이 조회되었다.
- SELECT SQL도 같은 트랜잭션을 사용하기 때문에 저장한 데이터를 조회할 수 있다. 다른 트랜잭션에서는 해당 데이터를 확인할 수 없다.
- 여기서 assertThat() 으로 검증이 모두 끝난다.
- @Transactional 이 테스트에 있으면 테스트가 끝날때 트랜잭션을 강제로 롤백한다.
- 롤백에 의해 앞서 데이터베이스에 저장한 item1 , item2 , item3 의 데이터가 제거된다.
*참고*
위의 상황에서 테스트에서 Commit을 하고 싶을 때는
메서드 레벨에서 @Commit 이나 @Rollback(value = false)를 사용하면 된다.
테스트 - 임베디드 모드 DB
위에서 데이터베이스의 분리 방법에 대해서 포스팅 되어있는데, H2 DB 같은 경우 임베디드 모드를 지원한다.
이는 인메모리 처럼 DB를 사용할 수 있게해주는 기능으로써 테스트 케이스를 실행하기 위해서 별도의 데이터베이스를 설치하고, 운영하는 것은 상당히 번잡한 작업을 안하게 해준다. 하지만 테이블을 작성하는 SQL을 따로 작성해주어야 한다.
임베디드 모드 DB 설정
@SpringBootApplication
public class Application {
@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;
}
}
- jdbc:h2:mem:db : 이 부분이 중요하다.
- 데이터소스를 만들때 이렇게만 적으면 임베디드 모드(메모리 모드)로 동작하는 H2 데이터베이스를 사용할 수 있다..
- DB_CLOSE_DELAY=-1 : 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는데, 그것을 방지하는 설정이다.
- 이 데이터소스를 사용하면 메모리 DB를 사용할 수 있다.
스프링부트의 SQL 스크립트 사용해서 임베디드 DB 테이블 초기화
src/test/resources/ 경로에 schema.sql 파일을 생성 후 테이블 SQL을 작성해두면 DB가 생성되면서 해당 스크립트도 같이 실행된다.
스프링 부트의 임베디드 DB 자동
test의 application.properties 파일의 datasource 설정을 없애고 위에서 작성한 임베디드 DB 설정 코드도 주석 처리해본 뒤 실행해도 스프링 부트에서는 자동으로 임베디드 DB를 만들어서 테스트 하게 해준다.
참고로 로그를 보면 다음 부분을 확인할 수 있는데 jdbc:h2:mem 뒤에 임의의 데이터베이스 이름이 들어가 있다. 이것은 혹시라도 여러 데이터소스가 사용될 때 같은 데이터베이스를 사용하면서 발생하는 충돌을 방지하기 위해 스프링 부트가 임의의 이름을 부여한 것이다.
conn0: url=jdbc:h2:mem:d8fb3a29-caf7-4b37-9b6c-b0eed9985454
임베디드 데이터베이스 이름을 스프링 부트가 기본으로 제공하는 jdbc:h2:mem:testdb 로 고정하고 싶으면 application.properties 에 다음 설정을 추가하면 된다.
spring.datasource.generate-unique-name=false
'Spring > JDBC' 카테고리의 다른 글
[Spring DB] JPA - 2 (설정과 사용) (0) | 2024.08.19 |
---|---|
[Spring DB] JPA - 1(ORM 개념) (0) | 2024.08.19 |
[Spring DB] Mybatis (0) | 2024.08.12 |
[Spring DB] Jdbc Template (0) | 2024.08.12 |
[Spring DB 개념] 스프링 예외 추상화 (0) | 2024.08.12 |