mybatis 하기 전에 테스트 코드에 db와 연결해서 하는 방법을 알아볼거다.
메인의 설정 파일에 url을 db에 연결했던것 처럼 test 패키지에 test용 프로퍼티를 만들면 그거 따라 진행된다.
@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);
}
}
앞에 @SpringBootTest가 붙어있으면 @SpringBootApplication이 붙은 곳을 찾아가 해당 설정으로 사용한다. 즉 실제 사용하려는 환경을 찾아 테스트 한다는 것.
일단 테스트를 실행하면 되는게 있고 안되는게 있는데, 되는건 저장하고 수정하고 하는 코드고 안되는건 저장했을 때 테스트에서 저장한 만큼만 조회가 되어야 하는데 db를 그대로 사용하므로 모든게 불러와진다. 이유는 테스트할 때 db에 아무것도 없다고 가정하고 만들었기 때문.
그래서 테스트할 때는 db와 분리해서 사용하는걸 알아보자. 가장 간단한건 테스트용 데이터베이스를 따로 만드는 것이다.
그래서 맨 처음에 h2에서 데이터베이스 만들었던것 처럼 테스트용 데이터베이스도 만들자.
스프링의 테스트 설정도 이 쪽으로 연결한다.
하지만 이것도 처음에는 성공하지만 두번째부턴 실패한다. 데이터베이스는 분리했지만 테스트 할 때마다 초기화하지 않기 때문.
여기서 테스트에 대한 중요한 개념을 알 수 있는데, 테스트에서 매우 중요한 원칙은 다음과 같다.
테스트는 다른 테스트와 격리해야 하며, 반복해서 실행할 수 있어야 한다.
현재는 이전에 실행했던게 남아있어서 반복해서 실행하는게 안된다. 그래서 끝날때마다 추가한 데이터에 delete sql을 사용해도 되지만 만약 테스트가 실행되는 도중 예외가 발생하거나 애플리케이션이 종료돼서 delete sql가 호출되지 않은 시점에 종료되면 그대로 남아있는건 변하지 않기 때문에 근본적인 해결책이 아니다.
그럼 이 문제를 어떻게 해결하냐? 데이터 롤백 전략을 쓸거다.
트랜잭션 매니저를 시작해서 beforeEach에서 트랜잭션을 시작하고 afterEach에서 롤백한다. PlatformTransactionManger라는걸 사용하고 있는데 기존에 썼던 DataTransactionManager에서 기능만 조금 추가된 확장된 클래스다. 이게 가능한 이유는 원래 레퍼지토리 안에 들어있는 코드들도 트랜잭션 메니저에서 커넥션을 가져다 쓰기 때문. 템플릿 안에 매니저를 통해 status를 이용해서 트랜잭션 메니저에서 같은 세션을 가져오고 사용하고 반납하고.. 했던걸 떠올려보자.
그럼 잘 됨을 볼 수 있다.
근데 이렇게 각 forEach에다가 세션용 status 지정해서 forAfter에 트랜잭션 롤백 코드 넣고 할 필요 없이 @Transactional만 붙이면 된다. 스프링에서 @Test가 붙은 @Transactional은 특별하게 작동해서 시작하고 끝날 때 자동으로 롤백한다.
이 @Transactional이 클래스에 붙어있으면 해당 메소드에는 자동으로 다 적용되는거고 각 함수에 붙여도 된다.
그럼 이 @Transactional이 붙은 테스트를 실행하는데 그 안 서비스 로직에서도 @Transactional이 붙어있으면 어떻게 되냐? 자세한건 뒤에서 배우는데 일단 같은 커넥션을 사용해서 트랜잭션도 같이 된다고 이해하면 된다함.
만약 DB에 정말 되는지 확인하고 싶으면 @Commit이나 @Rollback(false)를 앞에 붙여 테스트해보면 된다.
메모리에 올려서 테스트하는것도 있는데 이걸 더 많이 쓰는 것 같다. 이걸 임베디드 모드라고 하는데, 테스트 케이스를 위해서 별도의 데이터베이스를 설치하고 운영하는게 싫어서 사용하는 거다. h2는 자바로 개발되어서 JVM 안에서 메모리로 동작하는 특별한 기능을 제공하기 때문에 애플리케이션을 실행할 때 h2 데이터베이스도 해당 JVM 메모리에 포함해서 함께 실행할 수 있다. 안에서 같이 실행되서 임베디드 모드라고 하는거다. 메모리로 실행되기 때문에 애플리케이션이 종료되면 함께 종료되서 잔류도 걱정할 필요 없다.
사용방법은 애플리케이션에 추가하면 됨.
프로필이 "test" 일 때만 실행되기ㅔ 하고, h2:mem:db; 로 설정해서 메모리에 실행하게 한다. DB_CLOSE_DELAY=-1은 임베디드 모드에서는 데이터베이스 커넥션 연결이 모두 끊어지면 데이터베이스도 종료되는데, 그것을 방지하는 설정이다.
메모리에 h2 데이터베이스를 같이 올려 실행하는 거기 때문에, h2를 종료한 상태에서도 테스트를 할 수 있다. 다만 지금 테스트해보면 JVM 메모리에서 막 만들어진 데이터베이스라 테이블이 없다고 에러가 뜬다.
그래서 이것도 @ForEach에서 db template의 execute() 같은거 사용해서 테이블을 직접 만드는 코드를 넣을수도 있겠지만 이런 일이 있을 줄 알고 스프링에서 이미 만들어놓았다. test/resources/schema.sql에 초기화를 원하는 스크립트를 작성하면 된다. 이름 틀리면 안된다.
그럼 스프링 테스트를 시작할 때 저 코드를 실행하면서 시작해서 테이블이 만들어지고, 테스트가 제대로 되는걸 볼 수 있다.
근데 사실 스프링 기본값이 db url이 없으면 메모리에서 실행하는거다. 그래서 다 주석처리하고 기본값으로 실행해도 잘 된다.
그럼 지가 메모리에 데이터베이스 만들고 한다. 즉 테이블 만드는 스크립트랑 @Transactional만 잘 하면 되는것.
'CS > 김영한 스프링 강의' 카테고리의 다른 글
스프링 DB 2편 - 데이터 접근 핵심 원리 - 섹션5. 데이터 접근 기술 - JPA (0) | 2023.08.22 |
---|---|
스프링 DB 2편 - 데이터 접근 핵심 원리 - 섹션4. 데이터 접근 기술 - MyBatis (0) | 2023.08.20 |
스프링 DB 2편 - 데이터 접근 핵심 원리 - 섹션2. 데이터 접근 기술 - 스프링 JdbcTemplate (0) | 2023.08.19 |
스프링 DB 2편 - 데이터 접근 핵심 원리 - 섹션1. 데이터 접근 기술 - 시작 (0) | 2023.08.19 |
스프링 DB 1편 - 데이터 접근 핵심 원리 - 섹션6. 스프링과 문제 해결 - 예외 처리, 반복 (0) | 2023.08.17 |