💋 테스트 데이터 격리의 필요성
테스트 코드를 통해 아래 코드를 실행했다.
@Test
void findItems() {
Item item1 = new Item("gitchan", 1000, 10);
Item item2 = new Item("irene", 1000, 20);
Item item3 = new Item("oing", 1000, 30);
itemRepository.save(item1);
itemRepository.save(item2);
itemRepository.save(item3);
List<Item> result = itemRepository.findAll();
assertThat(result).containsExactly(item1, item2, item3);
}
하지만 실패했다.
이유는, findAll로 처음에 저장한 세 개의 데이터를 기대했지만, 그외에 약 10개의 데이터가 더 발견되었기 때문이다.
(에러 메세지는 캡쳐하지 못함)
왜일까?
💋 테스트의 실행 환경을 확인해보자!
@SpringBootTest
class ItemRepositoryTest {
// ...
}
테스트 코드는 먼저 src/test에 있는 application.properties를 우선으로 실행하게 된다.
당시 내 src/test 패키지의 application.properties로 별도의 설정을 해주지 않았기 때문에, src/main의 파일을 보게 되었을 것이다.
src/main application.properties는 h2 데이터베이스를 사용하고 있었다.
다음으로 테스트 클래스를 보면, @SpringBootTest 어노테이션이 붙어있다.
@SpringBootTest 어노테이션은 자동으로 @SpringBootApplication를 찾아 설정으로 사용하는데...
로컬에서 사용하고 있는 애플리케이션 서버와 테스트에서 동일한 베이스를 사용하고 있는 것이다!
테스트는 다른 환경과 철저하게 분리해야 한다.
어떻게 해야할까..?
✔ 테스트용 데이터 베이스를 분리한다.
가장 간단한 방법이다.
아래 방법은 h2에 대한 예시이다.
src/main, src/test 패키지에 각각 들어있는 application.properties의 datasource url을 다르게 설정한다.
main > application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.username=sa
test > application.properties
spring.datasource.url=jdbc:h2:tcp://localhost/~/testcase
spring.datasource.username=sa
이렇게 하면, 프로덕션 코드/테스트 코드 간의 데이터베이스는 격리할 수 있다.
하지만 여전히 여러 개의 테스트 코드 간의 격리는 하지 못한다.
하나의 테스트 메서드를 완료하면 데이터를 초기화해야 한다!
테스트는 다른 테스트와 격리할 수 있어야 하고, 반복해서 실행할 수 있어야 한다.
💋 테스트 코드 간 데이터 격리
✔ 테스트가 끝날 때마다 DELETE SQL을 사용해서 데이터를 삭제한다.
가장 간단하게 생각할 수 있는 방법이다.
@AfterEach 메서드를 생성해서, DELETE SQL을 작성한 후 실행시키면 된다.
@AfterEach
void tearDown() {
jdbcTemplate.execute("delete from item");
}
여전히 문제가 있다.
중간에 예외가 발생하거나, 애플리케이션이 종료되면 테스트 종료 시점에서 delete sql을 실행할 수 없어서 데이터가 삭제되지 않을 수 있다.
✔ 테스트가 끝날 때마다 트랜잭션을 강제로 롤백한다.
테스트가 종료되고 나서, 트랜잭션을 강제로 롤백해버리면 된다!
만약 중간 예외로 인해 애플리케이션이 종료되어서 롤백을 직접 호출하지 못하더라도 commit은 되지 않기 때문에 데이터 베이스에 변화가 생기지 않는다.
먼저, 적용하고자 하는 테스트 클래스에 PlatformTransactionManager를 의존성 주입받는다.
@Autowired
PlatformTransactionManager transactionManager;
그리고 매 테스트의 시작 시점에 트랜잭션을 시작하고, 끝나는 시점에 롤백을 호출하면 되는 것이다!
@BeforeEach
void setUp() {
// start transaction
status = transactionManager.getTransaction(new DefaultTransactionAttribute());
}
@AfterEach
void tearDown() {
// transaction rollback
transactionManager.rollback(status);
}
✔ 테스트가 끝날 때마다 @Transactional을 사용해서 데이터를 롤백한다.
테스트에서 사용되는 경우는 프로덕션 코드에서 사용할 때와 조금 다르다.
프로덕션 코드에서 사용되는 @Transactional은 성공 시 커밋, 실패 시 롤백하지만
테스트 코드에서 사용되는 경우에는 테스트를 트랜잭션을 실행하고, 테스트가 종료되는 경우에 자동 롤백해버린다.
(위에서 직접 강제 롤백하던 코드와 동일하게 작동한다.)
스프링이 이 어노테이션이 테스트에 붙어있는지 확인하고, 롤백을 하는 것이다.
방법은 너무 간단함.
메서드 위에 @Transactional 어노테이션만 붙이면 된다.
전체 메서드에 대해서 적용하고 싶다면, 아래처럼 클래스에 붙여도 된다.
@Transactional
@SpringBootTest
class ItemRepositoryTest {
// ...
}
끗
'TEST' 카테고리의 다른 글
[TEST] RestAssured: API를 테스트해보자! (0) | 2023.06.05 |
---|---|
[TEST] 인수 테스트 (Acceptance Test): ATDD, 인수테스트란? 인수테스트의 장점, 사용법 (0) | 2023.05.24 |