테스트 환경 분리
개요
통합 테스트를 작성했다. 로컬에선 문제없이 통과했지만 CI환경에서는 통과하지 못했다.
로그를 살펴보니 테스트 클래스 1이 통과되고 테스트 클래스 2는 중복된 값이라는 예외가 생겨 통과하지 못했다.
이 결과는 통합 테스트끼리 서로 간섭을 일으킨다는 의미가 된다.
원인
스프링 테스트에서는 같은 Context를 사용하는 테스트가 존재할 경우 기존의 Context를 재활용한다.(로컬에선 모든 테스트를 수행하지 않고 하나씩 수행해서 몰랐던 것이다.)
따라서 테스트 환경에서의 application.yml의 설정대로 테스트 데이터베이스를 구성한다. h2를 사용할 경우 디폴트로 create-drop으로 설정되어있고 따라서 데이터베이스 스키마가 한 번 만들어진 상태에서 이를 재활용한다.
그래서 중복된 값이라는 예외가 생겼던 것이다.
해결 방법
- 같은 데이터베이스가 여러 개의 테스트 클래스에서 사용되기 때문에 하나의 테스트가 끝나면 테스트 데이터를 삭제하는 식으로 동작하게 할 수 있다. 이를 위한 애노테이션이
@AfterEach
다.@AfterEach
는 각 테스트 메소드가 끝난 다음 실행시킬 로직으로 만약@BeforeEach
로 데이터를 넣었다면@AfterEach
로 삭제하는 로직을 구성하여 삭제하는 것이다. - 만약 데이터베이스와 연동하여 테스트하는 경우
@Transactional
애노테이션을 사용하여 커밋하지 않고 롤백시킬 수 있다. 그러나 여전히 하나의 데이터베이스를 사용하기 때문에 이 방법을 사용한다면 데이터베이스에 들어간 데이터는 pk를 가졌던 상태라 이 다음으로 실행되는 테스트는 이전 테스트에서 사용했던 값과는 다른 값을 사용해야한다. 예를 들어assertThat(sampleRepository.findById(1L)).isEqualTo(sample);
와 같이 작성하고 테스트한 경우 통과가 되지만 다음 테스트에서 똑같은 값을 사용한다면 테스트에 실패한다. 롤백을 시켜도 1L이라는 PK는 이미 할당된 적이 있기 때문이다. - 테스트 스프링 컨텍스트를 분리하는 방법이 있다. 스프링 테스트에서는 같은 컨텍스트를 사용하는 테스트가 존재할 경우 이를 캐싱하여 재사용한다.
@DirtiesContext
는 테스트가 컨텍스트에 영향을 준 경우 컨텍스트가 더럽혀졌다 판단하고 초기화를 진행하도록 하는 애노테이션이다.
DirtiesContext
DirtiesContext는 클래스 레벨 또는 메소드 레벨에 적용할 수 있는 애노테이션을 여러 옵션을 적용할 수 있다.(애노테이션을 사용할 때는 상세 코드를 보면 빠르게 파악할 수 있다.)
AFTER_METHOD
: 메소드 레벨에 적용할 시 별도로 설정해 주지 않아도 적용되는 옵션으로 메소드가 끝날 때 컨텍스트를 새롭게 로드한다.BEFORE_METHOD
: 메소드가 시작되기 전에 컨텍스트를 새롭게 로드한다.AFTER_CLASS
: 클래스 레벨에 적용할 시 디폴트로 적용되는 옵션으로 하나의 테스트 클래스가 종료되면 컨텍스트를 새롭게 로드한다.BEFORE_EACH_TEST_METHOD
: 하나의 테스트 클래스 내부의 메소드가 끝날때면 컨텍스트를 새롭게 로드한다.AFTER_EACH_TEST_METHOD
: 테스트 클래스 내부의 각 메소드가 끝날 때 컨텍스트를 새롭게 로드한다.
다음과 같이 사용한다.
@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_EACH_TEST_METHOD)
그러나 이 방법은 매번 컨텍스트를 새롭게 로드하기 때문에 테스트시 지연이 생긴다.
쉽게 말하면 매 테스트 클래스, 테스트 메소드를 실행할 때마다 애플리케이션을 새롭게 빌드하고 실행시키는 것이나 다름이 없다. 따라서 테스트가 많지 않다면 상관 없겠지만 테스트가 많다면 테스트가 굉장히 오래 걸린다.
이럴 때 사용할 수 있는 것이 직접 쿼리를 날려서 테이블을 날린 뒤 재생성하는 것이다.
@Sql
애노테이션은 테스트 전후에 SQL스크립트를 실행할 수 있도록 해주는 애노테이션이다. 별도의 스크립트 파일을 작성하고 테스트에 필요한 데이터를 미리 넣어둔다거나 테스트 후 데이터를 정리하는데 사용할 수 있다.
다음과 같이 사용한다.
@Sql(scripts = "/test-schema.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD)
@DirtiesContext
와 마찬가지로 언제 스크립트를 실행시킬 것인지 설정할 수 있다.
별도의 sql파일을 만들어서 다음과 같이 작성해주면 된다.
TURNCATE TABLE POST;