스프링 애플리케이션을 통합 테스트하려면 애플리케이션 컨텍스트에 선언된 빈을 가져와야 함. 스프링 테스트 지원 기능 없이 작성하면 (JUnit의 @Before, @BeforeClass를 붙인) 테스트 초기화 메서드에서 애플리케이션 컨텍스트를 직접 수동으로 로드해야 함. 각 테스트 메서드/클래스마다 실행 이전에 초기화 메서드를 호출하면 동일한 애플리케이션 컨텍스트가 여러 번 로드. 빈 개수가 많은 대규모 애플리케이션은 컨텍스트를 로드하는 데도 많은 시간이 소요
스프링 테스트 지원 기능을 활용하면 여러 빈 구성 파일로부터 애플리케이션 컨텍스트를 가져오거나, 여러 테스트에 걸쳐 컨텍스트를 캐시하는 등 테스트용 애플리케이션 컨텍스트를 관리하는 데 여러모로 유용
애플리케이션 컨텍스트는 단일 JVM 안의 모든 테스트에 걸쳐 구성 파일 위치를 키로 하여 두루 캐시
같은 애플리케이션 컨텍스트는 한번만 로드하면 되므로 테스트 실행 속도가 현저히 빨라짐
일부 테스트 실행 리스너는 테스트 컨텍스트 프레임워크에 기본 등록
기본 테스트 실행 리스너
| 테스트 실행 리스너 | 설명 |
|---|---|
| DependencyInjectionTestExecutionListener | 애플리케이션 컨텍스트를 비롯한 모든 의존체를 테스트에 주입 |
| DirtiesContextTestExecutionListener, DirtiesContextBeforeModesTestExecutionListener | @DirtiesContext 처리를 담당하며 필요 시 애플리케이션 컨텍스트를 다시 로드 |
| TransactionalTestExecutionListener | 테스트 케이스의 @Transactional을 처리하며 테스트 끝부분에서 롤백을 수행 |
| SqlScriptsTestExecutionListener | @Sql을 붙인 테스트를 감지해서 테스트를 시작하기 전에 주어진 SQL을 실행 |
| ServletTestExecutionListener | @WebAppConfiguration이 발견되면 웹 애플리케이션 컨텍스트를 로드 |
테스트 컨텍스트 프레임워크에서 애플리케이션 컨텍스트를 관리하려면 내부적으로 테스트 클래스와 테스트 컨텍스트 관리자를 연계
ApplicationContextAware 인터페이스를 궈현해 테스트 컨텍스트 관리자와 연계되므로 protected applicationContext를 사용해 애플리케이션 컨텍스트를 가져오기
| 테스트 프레임워크 | 테스트 컨텍스트 지원 클래스 |
|---|---|
| JUnit | AbstractJUnit4SpringContextTests |
| TestNG | AbstractTestNGSpringContextTests |
테스트 프레임워크에 맞는 테스트 컨텍스트 지원 클래스를 상송해 테스트 클래스를 작성
테스트 컨텍스트 지원 클래스는 DependencyInjectionTestExecutionListener, DirtiesContextTestExecutionListener, ServletTestExecutionListener 세 TestExecutionListener 구현체를 활성화
JUnit과 TestNG를 사용하면 테스트 컨텍스트 지원 클래스를 상속하지 않고도 테스트 클래스를 테스트 컨텍스트 관리자에 연계하고 직접 ApplicationContextAware 인터페이스를 구현 가능
테스트 클래스가 테스트 컨텍스트 프레임워크 체게에 종속되지 않으므로 작성한 클래스를 상속 가능
JUnit에서는 SpringRunner라는 테스트 실행기로 테스트 컨텍스트 관리자를 연계하여 테스트를 실행하면 되지만 TestNG에서는 테스트 컨텍스트 관리자를 직접 수동으로 연계
구성 클래스에 AccountService, AccountDao 인터페이스 선언 후 통합 테스트 작성
@Configuration
public class BankConfiguration {
@Bean
public InMemoryAccountDao accountDao() {
return new InMemoryAccountDao();
}
@Bean
public AccountServiceImpl accountService() {
return new AccountServiceImpl(accountDao());
}
}
JUnit으로 테스트 컨텍스트 프레임워크를 이용해 테스트를 작성할 때 애플리케이션 컨텍스트에 액세스하는 두 가지 방법
1: ApplicationContextAware 인터페이스를 구현하거나 ApplicationContext형 필드에 @Autowired를 붙여 자동연결
테스트 SpringRunner를 실행할 스프링 전용 테스트 실행기를 지정
클래스 레벨 애너테이션 @RunWith를 사용해 지정
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests implements ApplicationContextAware {
private static final String TEST_ACCOUNT_NO = "1234";
private ApplicationContext applicationContext;
private AccountService accountService;
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
@Before
public void init() {
accountService = applicationContext.getBean(AccountService.class);
accountService.createAccount(TEST_ACCOUNT_NO);
accountService.deposit(TEST_ACCOUNT_NO, 100);
}
@Test
public void deposit() {
accountService.deposit(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
}
@Test
public void withDraw() {
accountService.withdraw(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
}
@After
public void cleanup() {
accountService.removeAccount(TEST_ACCOUNT_NO);
}
}
구성 클래스는 클래스 레벨에 붙인 @ContextConfiguration의 classes 속성값으로 지정
XML 구성 파일에서는 locations 속성을 대신 사용
따로 설정이 없으면 테스트 컨텍스트가 알아서 찾음
테스트 클래스 명에 -context.xml을 붙인 이름의 파일이 테스트 클래스와 동일한 패키지에 있으면 그 파일을 로드
없으면 @Configuration을 붙인 모든 public static 내부 클래스의 테스트 클래스를 샅샅이 뒤져보고 파일이나 클래스가 발견되면 그 내용으로 테스트 구성을 로드
기본적으로 애플리케이션 컨텍스트는 각 테스트 메서드에서 캐싱되어 재사용
어떤 테스트 메서드에 @DirtiesContext를 붙이면 이 테스트 메서드가 끝나고 그다음 테스트 메서드에 컨텍스트를 재로드
2: JUnit에 종속된 테스트 컨텍스트 지원 클래스 AbstractJUnit4SpringContextTests 클래스를 상속하면 @RunWith(SpringRunner.class)는 이미 이 클래스에 선언되어 있어 하위 클래스에 따로 붙일 필요가 없음
@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceJUnit4ContextTests extends AbstractJUnit4SpringContextTests {
private static final String TEST_ACCOUNT_NO = "1234";
private AccountService accountService;
@Before
public void init() {
accountService = applicationContext.getBean(AccountService.class);
accountService.createAccount(TEST_ACCOUNT_NO);
accountService.deposit(TEST_ACCOUNT_NO, 100);
}
@Test
public void deposit() {
accountService.deposit(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
}
@Test
public void withDraw() {
accountService.withdraw(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
}
@After
public void cleanup() {
accountService.removeAccount(TEST_ACCOUNT_NO);
}
}
TestNG로 테스트 컨텍스트 프레임워크를 이용해 테스트를 작성할 경우, ApplicationContextAware 인터페이스를 구현한 지원 클래스 AbstractTestNGSpringContetTests를 상송해 애플리케이션 컨텍스트 가져올 수 있음
@ContextConfiguration(classes = BankConfiguration.class)
public class AccountServiceTestNGContextTests extends AbstractTestNGSpringContextTests {
private static final String TEST_ACCOUNT_NO = "1234";
private AccountService accountService;
@BeforeMethod
public void init() {
accountService = applicationContext.getBean(AccountService.class);
accountService.createAccount(TEST_ACCOUNT_NO);
accountService.deposit(TEST_ACCOUNT_NO, 100);
}
@Test
public void deposit() {
accountService.deposit(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 150, 0);
}
@Test
public void withDraw() {
accountService.withdraw(TEST_ACCOUNT_NO, 50);
assertEquals(accountService.getBalance(TEST_ACCOUNT_NO), 50, 0);
}
@AfterMethod
public void cleanup() {
accountService.removeAccount(TEST_ACCOUNT_NO);
}
}
TestNG 클래스가 테스트 컨텍스트 지원 클래스를 상속하는 모양이 마음에 들지 않으면 JUnit처럼 직접 ApplicationContextAware 인터페이스를 구현해 테스트 컨텍스트 관리자를 연계
AbstractTestNGSpringContextTests 소스 코드 참고