[스프링, 테스트] 스프링 테스트

스프링의 테스트

스프링 프레임워크는 애플리케이션의 단위 테스트와 통합 테스트를 더 쉽게 할 수 있도록 도와준다.


단위 테스트

단위 테스트를 하기 위해 스프링 프레임워크가 필요하지는 않다. POJO 객체와 JUnit과 같은 테스트 프레임워크만 있으면 단위 테스트가 가능하다. 또한 테스트 코드의 분리를 위해 목(mock) 객체 사용도 가능하다. 하지만 IoC 컨테이너와 의존성 주입을 사용하면 더 손쉬운 단위 테스트 코드 작성이 가능하다. 단위 테스트 시 테스트 대상 클래스의 의존 객체를 목 객체로 대체하여 테스트를 보다 손쉽게 수행할 수 있다.

스프링은 단위 테스트 시 스프링 프레임워크가 제공하는 기능 자체에 대한 테스트를 위해 Environment, JNDI, 스프링 웹 MVC 테스트를 위한 서블릿 API(Servlet API), 스프링 웹플럭스 테스트와 관련된 목 객체 생성을 위한 패키지를 제공한다.


통합 테스트

스프링 애플리케이션을 통합 테스트하려면 애플리케이션 컨텍스트에 선언된 빈을 가져와야 한다. 스프링은 통합 테스트를 위해 spring-test 모듈을 제공하며 org.springframework.test 패키지에는 스프링 컨테이너와의 통합 테스트를 위한 클래스들이 존재한다.

외부 서버와 연결된 애플리케이션의 경우 외부 환경이 구성되어 있지 않더라도 통합 테스트가 가능해야 한다. 즉, 테스트는 애플리케이션 서버나 다른 배포 환경에 의존하지 않는다.


통합 테스트 시 애플리케이션 컨텍스트 로딩

통합 테스트를 하기 위해서는 스프링 컨테이너로부터 애플리케이션 컨텍스트를 로드하여 컨텍스트에 등록된 컴포넌트(빈)들을 로드해야 한다. 스프링의 테스트 지원 기능이 없는 경우 테스트 초기화 메서드(JUnit의 @Before@BeforeClass를 붙인 메서드)에서 애플리케이션 컨텍스트를 직접 수동으로 로드 및 초기화해야 한다.

이 때 애플리케이션 컨텍스트는 테스트 수행 메서드마다 생성하지 않도록 한다. 테스트 전체에 걸쳐 하나의 애플리케이션 컨텍스트만 로드되도록 한다. 등록된 빈 개수가 많아질수록 애플리케이션 컨텍스트를 생성하는데에는 더 많은 시간과 리소스가 소모된다. 테스트 수행 시 애플리케이션 컨텍스트는 하나만 생성하고 여러 테스트 메서드가 공유하도록 하여 테스트 시간을 절약할 수 있다.

스프링의 테스트 지원 기능을 사용하면 애플리케이션 컨텍스트는 자동으로 한 번만 로드되어 테스트 실행 속도가 빨라진다. 스프링 테스트 컨텍스트 프레임워크는 스프링의 테스트 지원 기능을 제공한다.

스프링 테스트 컨텍스트 프레임워크는 여러 빈 구성 파일로부터 서로 다른 애플리케이션 컨텍스트를 가져오거나, 여러 테스트에 걸쳐 컨텍스트를 캐시하는 등 테스트용 애플리케이션 컨텍스트를 관리한다. 스프링의 테스트 프레임워크는 테스트 실행 시 애플리케이션 컨텍스트를 캐시한다. 애플리케이션 컨텍스트는 단일 JVM 안의 모든 테스트에 걸쳐 구성 파일 위치를 키로 하여 캐시된다. 테스트가 동일한 구성을 공유하는 경우 이러한 캐시 기능을 통해 애플리케이션 컨텍스트를 자동으로 한번만 로드하여 테스트의 성능 향상 이점을 제공한다.


통합 테스트를 위한 스프링의 테스트 컨텍스트 프레임워크

스프링 테스트 컨텍스트 프레임워크(org.springframework.test.context 패키지)는 사용 중인 테스트 프레임워크에 관계 없이 어노테이션 기반 단위 및 통합 테스트 지원을 제공한다.

테스트 컨텍스트가 필요한 이유는 다음과 같다. @Autowired를 통해 타입 기반 빈을 주입하는 경우 스프링 컨테이너에서 빈을 가져와야 한다. 해당 빈은 의존성 주입을 통해서 다른 의존 객체(외부 서비스 및 외부 환경)에 의존하고 있을 수 있다. 즉, 테스트 시 사용될 테스트 대상 객체 및 의존 객체는 실제 서비스에 사용되는 객체와 독립적으로 사용될 필요성이 있다. 실제 객체 대신 목 객체 및 스텁 객체를 의존 객체로 사용하여 테스트한다면 애플리케이션 컨텍스트를 로드할 필요 없이 해당 가짜 인스턴스를 테스트 전용 컨텍스트에 등록하여 사용하면 된다. 목/스텁 객체 대신 실제 애플리케이션 구동에 사용되는 빈 객체를 테스트 컨텍스트에 선택적으로 직접 등록하여 테스트할 수도 있다.

스프링은 테스트를 위한 애플리케이션 컨텍스트 관리 기능을 제공한다. 스프링 테스트 컨텍스트 프레임워크를 활용하는 JUnit은 테스트 전용 애플리케이션 컨텍스트(GenericApplicationContext)를 자동으로 만들어준다.


서브 모듈의 의존 객체 사용 시 테스트

멀티 모듈 프로젝트 구조에서 한 서브 모듈에서 다른 서브 모듈에 정의된 객체를 컨텍스트에 컴포넌트 등록 및 스캔하여 테스트하고자 하는 경우 해당 서브 모듈에 위치한 컴포넌트들의 패키지를 대상으로 컴포넌트 스캔을 위해 테스트 클래스에 다음과 같이 @ComponentScan 어노테이션을 설정한다.

@ComponentScan(basePackages = "서브모듈패키지")

루트 모듈의 빈 정의와 서브 모듈의 빈 정의가 모호할 경우 컨텍스트 빈 등록 시 문제가 될 수 있으므로 테스트 클래스 내에서 사용(의존성 주입)할 컴포넌트를 명시한다.

@Qualifier("테스트대상의존객체")


테스트 클래스에서 의존 객체 주입

테스트 클래스에서 테스트 대상 객체를 의존성 주입하기 위해 생성자 주입을 사용하는 경우 @Autowired를 생략하면 다음과 같은 에러가 발생할 수 있다.

No ParameterResolver registered for parameter

스프링은 테스트 클래스의 의존성 주입을 자동으로 수행하지 않으므로 @Autowired 어노테이션을 생성자에 명시적으로 지정하여 스프링 컨테이너에 의해 의존 객체의 테스트 클래스로의 적절한 의존성 주입이 이루어지도록 해야한다. 이는 스프링 컨테이너 대신 JUnit 테스트 프레임워크가 의존성 주입을 수행하므로 관련 어노테이션 생략 시 제대로 수행되지 않기 때문이다.


단위 테스트 시 프로파일 활성화

멀티 모듈 프로젝트 구조에서 루트 모듈과 서브 모듈에 존재하는 테스트 클래스의 테스트 메서드 실행 시 활성화되는 프로파일이 다를 수 있다. 루트 모듈(메인 클래스가 위치하는 모듈)의 애플리케이션 설정 파일에 프로파일을 활성화하는 설정(spring.profiles.active)이 있지만 서브 모듈에서는 해당 설정이 없는 경우가 이에 해당한다.

이 경우 서브 모듈의 애플리케이션 설정 파일에서 프로파일을 활성화하거나 테스트 클래스에 @ActiveProfiles 어노테이션을 설정하고 profiles 속성에 프로파일을 지정하면 된다.


테스트를 위한 애플리케이션 컨텍스트 구성

애플리케이션 실행 시 애플리케이션 컨텍스트를 어느 구성 클래스로부터 로드할지 지정하기 위해 @ContextConfiguration 어노테이션을 사용한다.

스프링 부트 애플리케이션을 테스트할 때, 스프링이 제공하는 슬라이스 테스트를 위한 @...Test 어노테이션을 사용하는 경우 로드할 구성 클래스를 명시적으로 정의하지 않을 경우 기본 구성을 자동으로 검색한다. 이때 기본적인 검색 알고리즘은 테스트 클래스가 포함된 패키지에서 @SpringBootApplication 또는 @SpringBootConfiguration 어노테이션을 설정한 클래스를 찾는 것이다.

@SpringBootApplication 어노테이션을 설정한 메인 클래스를 루트 패키지에 위치시킨다면 기본적으로 컴포넌트 스캔은 해당 루트 패키지를 대상으로 수행된다. @SpringBootApplication 사용 시 자동으로 활성화되는 @SpringBootConfiguration은 테스트 시 구성 탐지를 도와주는 @Configuration의 대안이다.

테스트 클래스의 패키지는 @SpringBootConfiguration 어노테이션이 지정된 클래스(주로 메인 클래스)가 위치한 패키지와 동일해야 한다. 그렇지 않을 경우 테스트 클래스의 @SpringBootTest 어노테이션은 테스트 실행 시 @SpringBootConfiguration 어노테이션 탐색, 컴포넌트 스캔 탐색에 실패하며 필요한 빈의 주입을 정상적으로 수행할 수 없다.


선택적 애플리케이션 컨텍스트 구성과 슬라이스 테스트

테스트 클래스에 @SpringBootTest 어노테이션 설정 시 테스트를 실행하면 기본적으로 모든 컴포넌트들을 컨테이너에 빈 등록하여 애플리케이션 컨텍스트를 완전히 구성한다. 애플리케이션에 정의된 모든 컴포넌트 중 일부 컴포넌트만 애플리케이션 컨텍스트에 불러오도록 하려면 @SpringBootTest 어노테이션의 classes 속성을 사용한다. 이 속성 설정은 @ContextConfiguration 어노테이션의 classes 속성 설정과 기능이 동일하다.

테스트 클래스에 @SpringBootTest를 적용하는 경우 테스트 시에도 애플리케이션 컨텍스트가 완전하게 구성된다. 이 대신 애플리케이션 컨텍스트를 일부만 구성하여 관련 빈들만 테스트에 사용함으로써 특정 기능만 부분적으로 테스트할 수 있는데 이러한 테스트를 슬라이스 테스트(slice test)라고 한다. 스프링은 spring-boot-test-autoconfigure을 통해 슬라이스 테스트를 위해 테스트 대상(기능) 별로 관련된 빈만 애플리케이션 컨텍스트에 자동 구성(등록)하는 선택적 컴포넌트 로딩 기능을 지원하는 다양한 어노테이션을 제공한다. 해당 어노테이션을 사용하면 테스트 대상(기능) 별로 관련된 빈만 애플리케이션 컨텍스트에 구성할 수 있다. 예를 들어, @DataJpaTest 어노테이션은 JPA와 관련된 컴포넌트들만 등록하여 테스트에 사용될 수 있도록 한다. 테스트할 기능을 사용할 수 있게 하고 그 외에 @Component이 설정된 다른 빈 정의를 무시한다. 테스트를 위한 자동 구성 설정을 사용자 정의할 수 있는 여러 @AutoConfigure... 어노테이션도 제공한다.


구성 클래스 세분화

슬라이스 테스트를 위한 어노테이션을 사용하는 경우, 메인 메서드가 정의된 애플리케이션 클래스에 특정 기능을 테스트하기 위한 구성 설정을 추가하지 않는 것이 좋다. 예를 들어, 몽고 데이터베이스에 대한 슬라이스 테스트 시 몽고 감사(auditing) 기능을 사용하려는 경우 @SpringBootApplication을 설정한 메인 애플리케이션 클래스에 @EnableMongoAuditing 어노테이션을 설정하는 대신 별도의 구성 클래스를 생성하고 관련 어노테이션을 적용하는 것이 좋다. 운영 애플리케이션의 구성을 위한 클래스가 여러 다양한 기능별 테스트를 위한 구성을 통합적으로 해서는 안 된다. 테스트하려는 기능 별로 구성 클래스를 세분화하면 테스트 시 필요한 특정 빈을 구성하기도 편하다.

여러 슬라이스 테스트를 수행하는 경우 특정 슬라이스 테스트에 대해 특정 구성 클래스를 선택적으로 활성화하기 위해 @Import 어노테이션을 사용할 수 있다. 특정 슬라이스 테스트에서만 필요한 빈을 해당 구성 클래스에 정의하거나 특정 어노테이션을 설정하고 테스트 클래스에 @Import 어노테이션을 사용하여 구성 클래스를 명시함으로써 해당 테스트에서만 활성화하면 된다.


테스트 시 AOP 적용

슬라이스 테스트를 위해 스프링 테스트 컨텍스트 프레임워크가 제공하는 @DataJpaTest와 같은 어노테이션을 사용하는 경우 애플리케이션 컨텍스트에 등록되지 않은 스프링 관리 빈들에 대해서는 필요 시 수동으로 인스턴스화하여 사용하게 되는데 이러한 객체들에 대해서는 프록시를 기반으로 동작하는 스프링의 AOP 기능을 사용할 수 없게 된다. AOP 기능을 사용하기 위해서는 프로그래밍 방식으로 테스트 클래스에서 프록시 팩토리를 통해 애스팩트를 적용하고 팩토리로부터 프록시 객체를 얻은 후 사용하면 된다.


참고

Comments