업데이트 :: 2018.08.22
스프링 테스트
기능
- Junit, TestNG 라는 테스팅 프레임워크 사용해서 스프링의 DI 컨테이너 동작
- 트랜젹션을 테스트 상황에 맞게 제어
- 애플리케이션 서버를 사용치 않고 스프링 MVC 동작을 재현
- 테스트 데이터를 적재하기 위해 SQL을 실행
- RestTemplate을 이용해서 HTTP 요청에 대한 임의의 응답을 보내는 기능
DI 컨테이너와 빈의 테스트
다음과 같은 애너테이션을 테스트 하는 모듈
- @Controller
- @Service
- @Repository
- @Component
Junit 설정
<dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <scope>test</scope> </dependency>
빈의 단위 테스트
- 단위테스트
- 스프링 DI 컨테이너 기능을 사용하지 않고 테스트 대상 클래스에서 구현한 로직으로만 테스트
- 외부 참조 없이 하드코딩된 고정 메시지를 반환하는 클래스를 Junit 테스트
@Service public class MessageService { public String getMessage() { return "Hello!"; } }
import static org.hamcrest.core.Is.*; import static org.junit.Assert.*; public class MessageServiceTest { @Test public void testGetMessage() { MessageService service = new MessageService(); String actualMessage = service.getMessage(); assertThat(actualMessage, is("Hello!")); } }
- 실제로는 고정메세지가 아닌 동적메시지를 가져오는 클래스가 일반적
MessageSource에서 메세지를 취득하는 클래스
@Service public class MessageService { @Autowired MessageSource messageSource; public String getMessageByCode(String code) { return messageSource.getMessasge(code, null, Localle.getDefault()); } }
- 실제 운영환경일 경우
- MessageSource에 ResourceBundleMessageSource를 사용해서 외부 정의된 메시지를 가져옴
의존컴포넌트의 모의화
<dependency> <groupId>org.mockito</groupId> <artifactId>mockito-core</artifactId> <scope>test</scope> </dependency>
import static org.hamcrest.core.Is.*; import static org.junit.Assert.*; import static org.mockito.Mockito.*; @RunWith(MockitoJUnitRunner.class) public class MessageServiceTest { @InjectMocks MessageService service; @Mock MessageSource mockMessageSource; @Test public void testGetMessageByCode() { deReturn("Hello!").when(mockMessageSource) .getMessage("greeting", null, Locale.getDefault()); // 테스트 String actualMessage = service.getMessageByCode("greeting"); assertThat(actualMessage, is("Hello!")); } }
- MockitoJUnitRunner를 이용 테스트할 컴포넌트(@InjectMocks)에 모의화한 컴포넌트(@Mock/@Spy)를 인젝션
- MessageSource의 Mock을 설정
- "greeting"이라는 코드가 지정될때 "Hello!"를 반환하도록 설정
DI 컨테이너에서의 빈의 통합테스트
- 단위테스트를 통과한 클래스는 스프링의 DI 컨테이너에 등록
- 다른 컴포넌트까지 통합된 상태에서 통합테스트를 진행
- 기본적으로는 DB와 같은 외부 리소스의 접근까지 포함해서 테스트
- Mock이나 Stub으로 대체가능
빈 정의파일 작성
@Configuration @ComponentScan("example.domain") public class AppConfig { @Bean // MessageSource의 빈정의 public MessageSource messageSource() { ResourceBundleMessageSource messageSource = new ResourceBundleMessageSource(); messageSource.setBasenames("messages"); return messageSource; } }
messages.properties
greeting=Hello!
의존성 주입
- 스프링 테스트 모듈 의존성 모듈에 추가
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-test</artifactId> <scope>test</scope> </dependency>
통합 테스트용 테스트 케이스 클래스
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(classes = AppConfig.class) public class MessageServiceIntegrationTest { @Autowired MessageService service; @Test public void testGetMessageByCode() { String actualMessage = service.getMessageByCode("greeting"); assertThat(actualMessage, is("Hello!")); } }
- @RunWith의 value속성에 Junit 테스트용 DI 컨테이너를 동작하기 위한 Runner 클래스를 지정
- @ContextConfiguration의 classes 속성에 DI 컨테이너가 사용하는 설정 클래스를 지정
- @Autowired를 사용해서 DI 컨테이너에 등록할 테스트 대상 빈을 인젝션
- 인젝션된 빈의 메서드를 호출해서 DI 컨테이너에 의해 의존 관계가 결합된 컴포넌트를 테스트
스프링 TestContext 프레임워크
- 스프링 테스트에서 테스팅 프레임워크에서 동작하는 테스트용 프레임워크의 기능
- 스프링 제공 애너테이션, 자바 표준애너테이션, 스프링 테스트 제공 테스트용 애너테이션등을 사용
스프링 JUnit 러너와 룰
- org.springframwork.test.context.junit4.SpringJUnit4ClassRunner
- JUnit에서 스프링 TestContext 프레임워크를 동작시키기 위한 지원 클래스
- @RunWith의 value 속성에 SpringJUnit4ClassRunner.class를 지정
- 단, @RunWith에는 하나의 Runner 클래스만 지정가능
- 서드파티에서 제공하는 Runner 클래스를 함께 사용 불가
- SpringClassRule과 SpringMethodRule을 활용하면 함께 사용할 수 있음
@RunWith(MockitoJUnitRunner.class) // 다른러너지정 @ContextConfiguration(classes = AppConfig.class) public class MessageServiceIntegrationTest { @ClassRule public static final SpringClassRule SPRING_CLASS_RULE = new SpringClassRule(); @Rulle public final SpringMethodRule springMethodRule = new SpringMethodRule(); }
DI 컨테이너 설정
- @org.springframwork.test.context.ContextConfiguration
- 스프링 TestContext 프레임워크에 DI 컨테이너를 생성
- classes 속성이나 localtions 속성으로 빈 정의 파일을 지정
- 다음 속성들은 생략이 가능
@ContextConfiguration(classes = AppConfig.class) public class MessageServiceIntegrationTest { }
기본 테스트 환경 설정
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration public class MessageServiceIntegrationTest { @Configuration static class LocalContext { // 빈정의 생략 } }
- @ContextConfiguration 속성 생략
- @Configuration이 붙은 static 설정 클래스 생성
- 만약 classes나 values를 명시하지 않았다면
- 테스트 케이스 클래스 안에 내부 클래스로 정의된 static 설정 클래스의 정보를 사용
- 명명 규칙을 만족하는 XML 파일 정보 사용
- MessageServiceIntegrationTest일 때
- com/example/domain/MessageServiceIntegrationTest-context.xml이라는 파일이 클래스 패스에 있어야함
웹 애플리케이션의 환경 설정
- @ContextConfiguration를 사용하는 방법외 @WebAppConfigration을 사용하는 방법
- 웹 애플리케이션 전용 DI 컨테이너를 만들 수 있음
- 프로젝트의 src/main/webapp 디렉터리가 웹 애플리케이션의 루트 디렉터리로 인식
- 메이븐이나 그레이들이 정한 표준 웹 애플리케이션의 루트 디렉터리와 같음
- 서블릿 API를 사용한 각종 목 객체도 테스트 케이스 클래스에 주입해서 활용 가능
- 사전 설정 작업이나 테스트 결과를 검증해야 할때, 서블릿 API를 사용해야 하는 상황에서 유용
- 종류
- MockServletContext
- MockHttpSession
- MockHttpServletRequest
- MockHttpServletResponse
@WebAppConfiguration public class WebApplicationIntegrationTest { @Autowired MockServletContext mockServletContext; @Autowired MockHttpSession mockHttpSession; @Autowired MockHttpServletRequest mockHttpServletRequest; @Autowired MockHttpServletResponse mockHttpServletResponse; }
DI 컨테이너의 라이프사이클 제어
DI 컨테이너의 캐시
- 기본적으로 같은 테스트 케이스 클래스의 테스트 메서드 간에는 같은 DI 컨테이너가 사용
- 테스트 케이스의 클래스가 다른 경우에도 @ContextConfiguration에 지정한 속성값이 같으면 캐시된 DI 컨테이너 사용
- @ContextConfiguration 빈정의 파일을 여러개 지정할때 순서에 유의
- 파일들이 같은파일이라도 지정하는 순서가 달라지면 앞에서 캐시된 것을 사용하지 못하고 새로운 컨테이너 생성
- 순서를 다르게 쓰기만 해도 불필요한 DI 컨테이너가 만들어지면ㅅ 테스트 시간이 길어지거나 메소리소모가 발생
DI 컨테이너의 파기
- 기본적으로 테스트에 사용되는 DI컨테이너는 자바 VM이 시작할 때 만들어 지고 종료될때 파기
- DirtiesContext를 이용해 제어
- 테스트 케이스의 클래스단위로 DI컨테이너를 제어하고 싶다면 클래스레벨에 @DirtiesContext 선언
- classMode 속성에 파기 타이밍을 지정
- 테스트 케이스 클래스의 테스트가 종료된 후 (기본값)
- 테스트 케이스 클래스의 테스트가 실행되기 전
- 테스트 케이스 클래스의 각 메서드가 실행되기 전
- 테스트 케이스 클래스의 각 메서드가 종료된 후
- classMode 속성에 파기 타이밍을 지정
테스트 케이스 클래스의 모든 테스트가 종료한 타이밍에 파기
@DirtiesContext publlic class MessageServiceIntegrationTest { }
테스트 메서드가 종료된 타이밍에 파기
@Test @DirtiesContext public void testGetMessageByCode() { }
프로파일 지정
- @ActiveProfiles를 사용해서 테스트
- 테스트할때 원하는 프로파일을 지정할 수 있음
@Configuration @Profile("dev") public class DevContext { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2).build(); } }
@Configuration @Profile("default") // 기본값(개발자 로컬 환경 외)의 빈 정의 public class DevContext { @Bean public DataSource dataSource() throws NamingException { JndiTemplate jndiTemplate = new JndiTemplate(); return jndiTemplate.lookup("jdbc/dataSource", DataSource.class); } }
- JUnit에서 테스트를 진행시 서버의 데이터소스를 사용할 수 없어 @ActiveProfiles을 통해 DevContext활성화
@ActiveProfiles("dev") // 적용할 프로파일 지정 public class AccountServiceIntegrationTest { }
- 프로파일을 명시적으로 지정하지 않았다면 'default'라는 이름의 프로파일이 기본으로 적용
- 앞서 예를 든 빈 정의 예에서 프로파일을 생략하면 DefaultContext가 활성화, 서버의 데이터소스를 사용
테스트용 프로퍼티 값 지정
- @TestPropertySource
- 프로파티에서 값을 가져오는 클래스에서 프로퍼티 값을 다양하게 바꿔가면서 테스트해야 할 때
- 시스템 프로퍼티 (자바 VM의 -D 옵션)
- 프로퍼티 파일
- 테스트 케이스의 클래스 단위로 테스트할 프로퍼티 값을 설정해 줄 수 있음
- 테스트 대상 클래스로 failureCountToLock의 값을 프로퍼티에서 가져오거나 없으면 기본값사용
- 프로파티에서 값을 가져오는 클래스에서 프로퍼티 값을 다양하게 바꿔가면서 테스트해야 할 때
public class AuthenticationService { @Value("${auth.failureCountToLock:5}") int failureCountToLock; }
- 애너테이션에 지정
- 프로퍼티 파일에 지정
애너테이션 지정
@TestPropertySource(properties = "auth.failureCountToLock=3") public class AuthenticationServiceIntegrationTest { }
프로퍼티에 지정
@TestPropertySource(location = "/test.properties") public class AuthenticationServiceIntegrationTest { }
auth.failureCountToLock=3
- location 속성과 properties 속성을 생략하려면 명명규칙을 만족하는 프로퍼티 파일이 사용
- AuthenticationServiceIntegrationTest -> com/example/domain/AuthenticationServiceIntegrationTest.properties
Created by MoonsCoding
e-mail :: jm921106@gmail.com
반응형
'Spring > DI & AOP' 카테고리의 다른 글
학습 // Spring // AOP // 표현식(SpEL) (0) | 2018.08.14 |
---|---|
학습 // Spring // AOP // Message (0) | 2018.08.14 |
학습 // Spring // AOP // Resource (0) | 2018.08.14 |
학습 // Spring // AOP // 프로퍼티(Property) (0) | 2018.08.08 |
학습 // Spring // AOP // 데이터바인딩(DataBinding) & 형변환(Casting) (0) | 2018.08.08 |