@DataJpaTest Rollback 후 Id 초기화 안됨
1. 문제
Hibernate를 사용해서 간단하게 Customer 객체를 디비에 저장하는 save()와 모든 유저를 조회하는 findAll() 그리고, 단일 조회할 수 있는 findById()를 만들어서 테스트 중에 있었다.
@Import(CustomerRepository.class)
@DataJpaTest
class CustomerRepositoryTest {
@Autowired
private CustomerRepository customerRepository;
@Test
public void findById_test() {
// Given
Customer customer = Customer.builder()
.name("코스")
.tel("0101111")
.build();
customerRepository.save(customer);
Long id = 1L;
// When
Customer customerPS = customerRepository.findById(id);
// Then
assertThat(customerPS.getName()).isEqualTo("코스");
}
@Test
public void save_test() {
// Given
Customer customer1 = Customer.builder()
.name("코스")
.tel("0101111")
.build();
Customer customer2 = Customer.builder()
.name("홍길동")
.tel("0102222")
.build();
// When
customerRepository.save(customer1);
customerRepository.save(customer2);
// Then
Customer customerPS = customerRepository.findById(1L);
System.out.println(customerPS);
assertThat(customerPS.getName()).isEqualTo("코스");
}
@Test
public void findAll() {
// Given
Customer customer1 = Customer.builder()
.name("코스")
.tel("0101111")
.build();
Customer customer2 = Customer.builder()
.name("임꺽정")
.tel("0101111")
.build();
customerRepository.save(customer1);
customerRepository.save(customer2);
// When
List<Customer> result = customerRepository.findAll();
// Then
assertThat(result.size()).isEqualTo(2);
assertThat(result.get(1).getName()).isEqualTo("임꺽정");
}
}
테스트를 하나하나 실행시켰을 때는 통과가 나왔지만, 전체 테스트를 돌려보니 계속 실패가 나왔다.
내가 처음에 생각한 로직은 다음과 같다.
- 테스트 메서드가 하나씩 실행되고 끝날 때 Transaction이 종료되고, Rollback을 해준다고 알고 있다. 이건 @DataJpaTest에 @Transactional이 붙어있기 때문이다.
- 그럼 각 메소드가 종료될 때 Rollback이 되니깐 Id(PK)도 당연히 다시 1부터 시작된다고 생각해서 모든 테스트를 같이 돌려도 완벽히 격리된 테스트를 할 수 있을 거라고 생각했다.
2. 분석
궁금해서 @AfterEach와 @BeforeEach를 사용해서 각 테스트의 시작 전과 끝난 후에 findAll() 결과를 출력해 봤다.
@BeforeEach
public void beforeCheck() {
System.out.println("===== Before Check =====");
check();
System.out.println("=======================");
}
@AfterEach
public void afterCheck() {
System.out.println("===== After Check =====");
check();
System.out.println("=======================");
}
public void check() {
List<Customer> result = customerRepository.findAll();
if (result.isEmpty()) {
System.out.println("비어있습니다.");
} else {
result.forEach(customer -> {
System.out.println(">>> " + customer.getId() + " " + customer.getName() + " " + customer.getTel());
});
}
}
# findById 테스트
===== Before check =====
비어있습니다.
=======================
===== After check =====
>>> 1 코스 0101111
=======================
# findAll 테스트
===== Before check =====
비어있습니다.
=======================
===== After check =====
>>> 2 코스 0101111
>>> 3 임꺽정 0101111
=======================
# save 테스트
===== Before check =====
비어있습니다.
=======================
===== After Check =====
>>> 4 코스 0101111
>>> 5 홍길동 0102222
=======================
출력 결과를 보면 다음 테스트가 시작되기 전에 이전 데이터는 Rollback이 되는 게 맞다. 근데 보면 Id 값은 1로 초기화되지 않는 것을 확인해 볼 수 있다.
혹시나 현재 h2 데이터베이스를 사용하고 있어서 그런 건가 하고 MySQL로 연동해서 동일하게 실행해 보니 동일한 결과가 나왔습니다.
이후에 조금 궁금해져서 MySQL을 실행해서 직접 테이블을 하나 만들고, 트랜잭션을 만들어서 Insert 후 동일하게 Rollback을 해봤습니다.
# 테이블 생성
create table member (
id bigint not null auto_increment,
name varchar(20),
primary key(id)
}
# 트랜잭션 시작
start transaction;
insert into member(name) values('오징어');
insert into member(name) values('김징어');
# 롤백 후 커밋
rollback;
commit;
# 다시 데이터 삽입
insert into member(name) values('이징어');
테스트 코드 상황과 동일하게 상황을 구성해서 직접 쿼리를 작성해봤는데, 롤백 후에도 Id가 1부터 시작되는 것이 아닌 3부터 시작돼서 저장되는 것을 확인해 볼 수 있었습니다.
3. 해결 방법
@AfterEach에서 각 테스트가 끝나면 Id의 시작점을 RESTART 해준다.
@AfterEach
public void check() {
em.createNativeQuery("ALTER TABLE customer ALTER COLUMN id RESTART")
.executeUpdate();
}
이 방법은 솔직히 좋은 방법은 아니라고 생각합니다. NativeQuery를 이렇게 사용해서 한다는 것은 특정 DB에 종속적인 테스트를 하고 있다는 것을 의미합니다.
사실 계속 방법을 찾다가 현재 작성한 테스트를 한 번에 실행했을 때 통과시켜보고 싶다는 생각에 찾아본 방법입니다.
4. 느낀 점
원하는 결과는 얻었지만, 좋지 않은 테스트라는 것은 분명하기 때문에 더 좋은 방법을 찾아봐야 할 거 같습니다.
테스트를 할 때는 Id 값을 저렇게 Magic Number를 사용해서 하는 방법도 좋지 않은 것 같습니다.
테스트에 관해서는 더 찾아보고, 학습해야될 요소라고 생각됩니다. 단순히 궁금점이 조금 생겨서 기초부터 파고 들어봤던 거 같습니다.