[Spring Database] 데이터베이스 테스트
데이터베이스를 연동한 상태에서 잘 동작하는지 확인하기 위해 테스트 코드를 작성 해 보자.
우선 test 디렉토리의 application.properties 파일에도 데이터베이스 관련 정보를 넣어 줘야 한다.
spring.profiles.active=test
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
logging.level.org.springframework.jdbc=debug
@SpringBootTest 애너테이션은 @SpringBootApplication 애너테이션을 찾아서 설정으로 사용한다.
테스트 시 로컬에서 사용하는 데이터베이스가 로컬에서 사용하는 데이터베이스가 겹치면서 테스트에서 문제가 발생한다.
테스트를 진행할 때는 다른 환경과 철저하게 분리해야 한다.
데이터베이스를 하나 더 만들고 설정 정보를 입력해주자.
각각의 테스트들은 다른 테스트들과 격리되어야 하고, 테스트는 반복해서 실행할 수 있어야 한다.
즉, 이전의 테스트 결과가 다음 테스트에 영향을 미쳐서는 안 된다.
@AfterEach 애너테이션으로 테스트가 끝날 때 마다 delete sql 을 사용해도 되지만, 이 방법을 사용하면 테스트 실행 도중 오류가 터져서 delete sql까지 도달하지 못 하는 경우를 잡지 못한다.
그러면 어떻게 해야 할까?
이 때 트랜잭션을 사용해 보자.
테스트를 실행하기 전에 트랜잭션을 시작하고, 테스트가 끝난 후 트랜잭션을 롤백시키면 혹시나 테스트가 실패해서 롤백을 호출하지 못해도 트랜잭션을 커밋하지 않기 때문에 데이터베이스를 깔끔하게 관리 할 수 있다.
@Transactional
@SpringBootTest
class ItemRepositoryTest {
@Autowired
ItemRepository itemRepository;
@AfterEach
void afterEach() {
//MemoryItemRepository 의 경우 제한적으로 사용
if (itemRepository instanceof MemoryItemRepository) {
((MemoryItemRepository) itemRepository).clearStore();
} // 데이터베이스 사용할 때는 초기화가 좀 위험하니까..
}
@Test
void save() {
//given
Item item = new Item("itemA", 10000, 10);
//when
Item savedItem = itemRepository.save(item);
//then
Item findItem = itemRepository.findById(item.getId()).get();
assertThat(findItem).isEqualTo(savedItem);
}
@Test
void updateItem() {
//given
Item item = new Item("item1", 10000, 10);
Item savedItem = itemRepository.save(item);
Long itemId = savedItem.getId();
//when
ItemUpdateDto updateParam = new ItemUpdateDto("item2", 20000, 30);
itemRepository.update(itemId, updateParam);
//then
Item findItem = itemRepository.findById(itemId).get();
assertThat(findItem.getItemName()).isEqualTo(updateParam.getItemName());
assertThat(findItem.getPrice()).isEqualTo(updateParam.getPrice());
assertThat(findItem.getQuantity()).isEqualTo(updateParam.getQuantity());
}
@Test
void findItems() {
//given
Item item1 = new Item("itemA-1", 10000, 10);
Item item2 = new Item("itemA-2", 20000, 20);
Item item3 = new Item("itemB-1", 30000, 30);
itemRepository.save(item1);
itemRepository.save(item2);
itemRepository.save(item3);
//둘 다 없음 검증
test(null, null, item1, item2, item3);
test("", null, item1, item2, item3);
//itemName 검증
test("itemA", null, item1, item2);
test("temA", null, item1, item2);
test("itemB", null, item3);
//maxPrice 검증
test(null, 10000, item1);
//둘 다 있음 검증
test("itemA", 10000, item1);
}
void test(String itemName, Integer maxPrice, Item... items) {
List<Item> result = itemRepository.findAll(new ItemSearchCond(itemName, maxPrice));
assertThat(result).containsExactly(items);
}
}
테스트 클래스에 @Transactional 애너테이션을 붙이면 모든 메서드에 트랜잭션이 적용된다.
원래 @Transactional 애너테이션은 로직이 성공적으로 수행되면 커밋도록 설계되어 있는데, 테스트에서는 좀 다르게 동작한다.
테스트에서 @Transactional 애너테이션을 사용하면 테스트를 트랜잭션 안에서 실행하고, 테스트가 끝나면 트랜잭션을 롤백시킨다.
테스트에서 트랜잭션을 사용해 테스트를 반복해서 실행할 수 있도록 했고, 각각의 테스트들이 격리되도록 했다.
가끔 롤백 대신 커밋되도록 설정해 데이터베이스에 값이 들어가는걸 확인하고 싶을 때가 있는데,
그럴 경우 @Commit 애너테이션 혹은 @Rollback(value = false) 애너테이션을 사용하면 된다.
지금까지 데이터베이스를 연동해서 테스트를 진행할 때 별도로 데이터베이스를 설치하고 운영했다.
단순히 테스트를 위한 데이터베이스라면 굳이 귀찮은 작업을 거치지 않고 간단하게 진행하는 방법이 있다.
H2 데이터베이스는 자바 언어로 개발돼있고 메모리 모드로 동작하는 특별한 기능을 제공한다.
이 기능을 사용하면 테스트 진행 시 자바 메모리처럼 데이터베이스를 애플리케이션에 내장해서 함께 실행할 수 있고, 이를 임베디드 모드라고 부른다. (Embeded)
@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;
}
프로필이 test 일 때 데이터소스를 스프링 빈으로 등록하고 사용한다. (테스트에서만 이 데이터소스를 사용할 예정이다)
jdbc:h2:mem : 임베디드 모드로 동작하는 H2 데이터베이스를 사용한다.
DB_CLOSE_DELAY=-1 : 임베디드 모드에서는 데이터베이스 커넥션이 모두 끊어지면 데이터베이스가 종료된다. 이를 방지하기 위해 추가한다.
이렇게 만들어 놓은 데이터소스를 사용하면 데이터베이스를 메모리처럼 사용할 수 있다. (이를 메모리 DB라고 부르자)
메모리 DB는 애플리케이션이 시작할 때 같이 시작되고, 애플리케이션이 종료될 때 같이 종료된다.
애플리케이션 실행 시점에 데이터베이스 테이블을 만들어서 넣어 줘야 하는데, JDBC / JdbcTemplate을 직접 사용해서 넣는건 너무 불편하다.
스프링 부트는 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)
);
src/test/resources/schema.sql (경로와 파일 명을 정확하게 설정하자)
애플리케이션 로딩 시점에 위의 SQL을 실행한다.
데이터베이스를 딱히 설정하지 않으면 스프링 부트는 임베디드 데이터베이스를 사용한다.
즉, 위에서 빈으로 등록할 필요 없이 테스트의 application.properties에서 데이터베이스 관련 설정을 지우면 된다.
(내부적으로 임베디드 모드로 접근하는 데이터소스를 만들어서 사용한다)
테스트에 @Transactional 애너테이션 붙이기만 하면 된다.
(application.properties 파일에 데이터베이스 관련 정보를 등록 할 필요도 없다)
'Spring > Spring Database' 카테고리의 다른 글
[Spring Database] JPA (1) | 2022.09.09 |
---|---|
[Spring Database] MyBatis (0) | 2022.09.09 |
[Spring Database] JdbcTemplate (0) | 2022.09.06 |
[Spring Database] 데이터베이스 예외 처리 (0) | 2022.09.05 |
[Spring Database] Transaction AOP (1) | 2022.09.01 |
댓글
이 글 공유하기
다른 글
-
[Spring Database] JPA
[Spring Database] JPA
2022.09.09 -
[Spring Database] MyBatis
[Spring Database] MyBatis
2022.09.09 -
[Spring Database] JdbcTemplate
[Spring Database] JdbcTemplate
2022.09.06 -
[Spring Database] 데이터베이스 예외 처리
[Spring Database] 데이터베이스 예외 처리
2022.09.05