테스트 환경 격리

June·2022년 5월 26일
1

우테코

목록 보기
48/84

학습 배경

미션이 콘솔 프로그램에서 웹을 이용하는 것으로 바뀌면서 조금씩 통제하지 못하는 것들이 생겨났다. 특히 스프링 프레임워크, DB가 대표적인 것들이었다.

그리고 이런 것들을 완벽히 통제하지 못하면서 테스트 코드가 돌아가는 순서에 따라서 실패하는 일들이 생겼다. 조금씩 테스트 코드를 작성하는 것이 부담되기 시작했을 때 테스트 격리 키워드를 강의에서 들어 정리해보게 되었다.

테스트 격리

테스트 격리를 다루고 있는 여러 블로그들이 공통적으로 마틴 파울러의 블로그에서 테스트 격리의 필요성을 인용하고 있었다.

테스트할 때마다 성공할 수도, 실패할 수도 있는 테스트는 비결정적 테스트이고 이런 테스트는 결국 테스트코드 자체의 필요성을 못느끼게 할 수 있어 나쁘다고 한다.

테스트를 신뢰할 수 있으려면 테스트 환경을 명확하게 통제할 수 있어야 한다.

테스트 환경을 통제할 수 있는 방법은 크게 두 가지로 소개된다.

  1. 매 테스트마다 테스트 환경을 부수고 새로 만들기
  2. 테스트를 하고나서 테스트 영향을 되돌리기 (rollback)

특히 순수 자바 객체의 단위테스트가 아닌 스프링 프레임워크를 이용해서 통합테스트를 하다보면 공유 자원들을 사용하게 된다. 이때 자원을 공유하기 때문에 예상과 다른 테스트 결과가 나오기도 하기 때문에 테스트 격리가 필요하다.

스프링이 처음 시작할 때 Application Context가 띄워지는데 이때 기본적으로 Application Context는 캐싱되어 재사용된다.

테스트 격리를 하기 전에는 @BeforeEach@AfterEach에서 데이터베이스에 변경된 데이터를 원래 수동으로 직접 돌려주었다.

DB 분리하기

뒤에 나오는 테스트 격리를 위해서 기본적으로 DB를 분리하는게 좋다.

테스트용 DB 분리하기를 참고하자.

DB에서 primary key를 auto increment로 했다면 롤백을 한다고해서 primary key는 되돌아가지 않는다. 그래서 실제 운영 DB와 분리할 필요가 있다.

@DirtiesContext

@DirtiesContext(classMode = DirtiesContext.ClassMode.BEFORE_EACH_TEST_METHOD)

공식문서 참조

이 애노테이션은 테스트와 관련된 ApplicationContext 가 '더러'워서 컨텍스트 캐시에서 제거되어야함을 의미한다.

만약 테스트가 내장 데이터베이스의 상태를 바꾸거나 싱글톤 빈의 상태를 바꾼다면 써야한다.

또 클래스 레벨에서 사용할 수도 있고 메서드 레벨에서도 사용할 수 있다.
옵션들을 이용해서 각 메서드들의 전에 컨텍스트를 새로 만들 것인지 후에 새로 만들것인지, 클래스 단위로 시행할지 등을 정할 수 있다.

별 다른 설정없이 매번 쉽게 테스트 격리를 할 수 있었지만 테스트 케이스들이 많아지니 확실히 시간이 많이 걸렸다. 매번 직접 컨텍스트를 새로 만들기 때문이다.

@Transactional

@Transactinoal 공식문서

테스트에 이 애노테이션을 붙이면 각 테스트 후 롤백된다.

인수테스트를 할 때는 이 애노테이션이 제대로 동작하지 않았는데, RANDOM_PORTDEFINED_PORT를 사용하면 클라이언트와 서버가 다른 스레드에서 돌아가게 된다. 따라서 서로 다른 트랜잭션이다. 그래서 롤백이 되지 않는 것이다.

@JdbcTest

기본적으로 @Transactional 애노테이션을 가지고 있어서 테스트 후 롤백된다.

@JdbcTest
class LineDaoTest {

    private final LineDao lineDao;
    
    @Autowired
    JdbcTemplate jdbcTemplate
    
    @BeforeEach
    public void setUp() {
        this.lineDao = new LineDao(jdbcTemplate);
    }
}

이름에서 알려주듯이 Jdbc 기반으로 테스트하는데 집중하고 있다. 다른 빈들을 생성하지 않고 오직 Jdbc 관련된 빈들만 생성해서 테스트를 준비한다.

따라서 @SrpingBootTest를 붙인 테스트에서는 @Autowired LineDao lineDao로 의존성 주입이 된 객체를 생성할 수 있었지만, @JdbcTest가 붙은 클래스에서는 JdbcTemplate이나 DataSourceAutowired로 얻어와서 직접 조립을 해주어야 한다.

그게 싫다면 위의 방식처럼 @Import를 하고 @Autowired로 dao 객체들의 의존성을 주입된 상태로 얻을 수도 있다.

@Sql

이 어노테이션을 이용하면 주어진 경로에 있는 sql 스크립트를 실행한다.
클래스에 붙이면 매 테스트 전에 실행을 하게된다. 스크립트에서 테이블을 삭제하고 새로 만든다면 테스트간 서로 영향을 주고 받지 않는다.

하지만 Drop 테이블을 이용하면 매번 새로 테이블 스키마를 만들어야한다. 테이블의 내용만 비우고 싶다면 TRUNCATE 명령어를 사용할 수도 있다.

지금까지의 방법 중 가장 합리적인 것 같지만 테이블이 만약 자주 바뀌는 상황이라면 스크립트 파일도 수정해주어야 한다는 단점이 있다.

참고

https://tecoble.techcourse.co.kr/post/2020-09-15-test-isolation/

1개의 댓글

comment-user-thumbnail
2022년 5월 26일

오오.. @Import 애너테이션 배워갑니다 토르!

@JdbcTest + 모든 Dao Import + 상속하여 각 DaoTest 클래스에서 부모 필드 사용
이렇게 하면 정말 깔끔해지겠네요!!

답글 달기