본문 바로가기

🧑🏻‍💻 Dev/오류해결

@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("임꺽정");
    }
}

테스트를 하나하나 실행시켰을 때는 통과가 나왔지만, 전체 테스트를 돌려보니 계속 실패가 나왔다.

내가 처음에 생각한 로직은 다음과 같다.

 

  1. 테스트 메서드가 하나씩 실행되고 끝날 때 Transaction이 종료되고, Rollback을 해준다고 알고 있다. 이건 @DataJpaTest에 @Transactional이 붙어있기 때문이다.
  2. 그럼 각 메소드가 종료될 때 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 전 Select

# 롤백 후 커밋
rollback;
commit;

Rollback 후 Select

# 다시 데이터 삽입
insert into member(name) values('이징어');

데이터 추가 삽입 후 Select

테스트 코드 상황과 동일하게 상황을 구성해서 직접 쿼리를 작성해봤는데, 롤백 후에도 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를 사용해서 하는 방법도 좋지 않은 것 같습니다.

테스트에 관해서는 더 찾아보고, 학습해야될 요소라고 생각됩니다. 단순히 궁금점이 조금 생겨서 기초부터 파고 들어봤던 거 같습니다.