2.4 스프링 테스트 적용
애플리케이션 컨텍스트 생성 방식
- @Before 메소드가 테스드 메소드 개수만큼 반복
- 반복될 때마다 애플리케이션 컨텍스트가 만들어짐
- 애플리케이션 컨텍스트 생성 시 모든 싱글톤 빈 오브젝트를 초기화하므로 제법 많은 시간을 필요로 함
테스트는 가능한 독립적으로 매번 새로운 오브젝트를 사용하는 것이 원칙
- 애플리케이션 컨텍스트처럼 시간과 자원이 많이 소모되는 경우 테스트 전체가 공유하는 오브젝트를 만들기도 함
- 다만, 이 때도 테스트는 일관성이 있어야하며, 테스트 순서에 영향을 받지 말아야한다.
- 애플리케이션 컨텍스트의 경우 초기화되고 나면 내부 상태가 변경되는 일이 거의 없으므로 무관함
문제는 JUnit이 매번 테스트 클래스의 오브젝트를 새로 생성함. 여러 테스트가 함께 참조할 애플리케이션 컨텍스트를 오브젝트 레벨에 저장해두면 곤란(Question : 음 ?)
- 스태틱 필드에 저장
- JUnit은 @BeforeClass 스태틱 메소드를 지원
- 스프링이 제공하는 애플리케이션 컨텍스트 테스트 지원 기능
- 이게 더 편리
2.4.1 테스트를 위한 애플리케이션 컨텍스트 관리
스프링 테스트 컨텍스트 프레임워크 적용
적용 방법
- @Before 메소드에서 애플리케이션 컨텍스트 생성코드 제거
- ApplicationContext 타입의 인스턴스 변수 선언 후 @Autowired 애노테이션 추가
- 클래스 레벨에 @RunWith와 @ContextConfiguration 애노테이션 추가
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class UserDaoTest {
@Autowired
private ApplicationContext context;
// ...
}
context를 초기화해주는 코드가 없음
- 그럼에도 불구하고 NullPointerException은 발생하지 ㅇ낳음
- context 변수에 애플리케이션 컨텍스트가 들어가 있음
- RunWith : JUnit 테스트 실행 방법 확장 시 사용하는 애노테이션
- SpringJUnit4ClassRunner : JUnit용 테스트 컨텍스트 프레임워크 확장 클래스, 테스트에서 사용할 애플리케이션 컨텍스트를 만들고 관리하는 작업을 해 줌
- ContextConfiguration : 자동으로 만들어 줄 애플리케이션 컨텍스트의 위치를 지정
테스트 메소드의 컨텍스트 공유
setUp()에 context와 자기 자신인 this를 출력하는 코드 추가 (임시 코드)
@Before
public void setUp() {
System.out.println("context: " + this.context);
System.out.println("setUp: " + this);
}
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@659a969b
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@41e1e210
context: org.springframework.context.support.GenericApplicationContext@4883b407
setUp: com.david.tobysspring.user.dao.UserDaoTest@625732
context는 세 번 모두 동일하지만, UserDaoTest 자신은 세 번 모두 새로 생성됨.
- 테스트 실행 전 딱 한 번 애플리케이션 컨텍스트를 생성해두고, 테스트 오브젝트가 생성될 때마다 특별한 방법으로 애플리케이션 컨텍스트를 특정 필드에 주입
- 일종의 DI(애플리케이션 오브젝트 사이의 관계를 관리하기 위한 DI와는 조금 다름)
테스트 클래스의 컨텍스트 공유
테스트 클래스가 여러 개 있을 때
- 같은 설정 파일을 가진 애플리케이션 컨텍스트를 사용하면, 테스트 클래스 사이에서도 애플리케이션 컨텍스트를 공유하게 해 줌
- 테스트 클래스의 수가 늘어나도 이 덕분에 테스트 성능이 대폭 향상 됨
@Autowired
스프링 DI에 사용되는 특별한 애노테이션
- @Autowired가 붙은 인스턴스 변수가 있으면, 테스트 컨텍스트 프레임워크는 변수 타입과 일치하는 컨텍스트 내의 빈을 찾는다.
- 타입이 일치하는 빈이 있으면 인스턴스 변수에 주입해준다.
- 일반적으로 생성자 또는 수정자 메소드가 필요하지만, 이 경우에는 없어도 주입이 가능하다.
UserDao 역시 Autowired를 이용하여 DI 받을 수 있다.
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/applicationContext.xml")
public class UserDaoTest {
@Autowired
private ApplicationContext context;
@Autowired
private UserDao dao;
// ...
}
@Autowired는 변수에 할당 가능한 타입을 가진 빈을 자동으로 찾음
- 다형성 이용 가능
- 같은 타입의 빈이 두 개 이상 있을 경우 변수의 이름과 같은 이름의 빈을 주입함
- 그래도 못 찾으면 예외 발생
2.4.2 DI와 테스트
UserDao와 커넥션 생성 클래스 사이 : DaoSource
- UserDao는 자신이 사용하는 오브젝트의 클래스가 무엇인지 알 필요가 없음
- DI를 통해 오브젝트를 주입받으므로 오브젝트 생성에 대한 부담이 없음
- 코드 수정 없이 의존 오브젝트를 바꿔가면 사용할 수 있음
테스트 코드에 의한 DI
기존 applicationContext.xml에 정의된 DataSource빈을 사용할 경우
- 운영용 DB와 연결이 되어 있다면? deleteAll()을 실행하고 짤리겠지? (짤리기만 하면 다행)
- DI를 이용하여 테스트 중에 DAO가 사용할 DataSource 오브젝트를 바꿔주는 방법을 이용
테스트용 DB에 연결해주는 DataSource : SingleConnectionDataSource 사용 (스프링이 제공하는 가장 빠른 DataSource)
- DBConnection을 하나만 만들어두고 계속 사용(빠름)
- 다중 사용자 이용 환경에서 사용하기는 어려움
- 순차적으로 진행되는 테스트 환경에서 이용하기 적절
UserDaoTest.java
@DirtiesContext
public class UserDaoTest {
@Before
public void setUp() {
DataSource dataSource = new SingleConnectionDataSource(
"jdbc:oracle:thin:@localhost:1521:xe", "springbook_test", "test", true);
dao.setDataSource(dataSource);
// ...
}
}
XML을 사용하지 않고도 오브젝트 관계를 재구성할 수 있다는 점에서 편리하지만, 매우 주의해서 사용해야 함.
- applicationContext.xml 파일의 설정정보를 따라 구성한 오브젝트를 가져와 의존관계를 강제로 변경
- 애플리케이션 컨텍스트는 한 번만 만들어지고, 모든 테스트에서 공유되므로 변경하지 않는 것이 원칙
- 따라서 Dirties 애노테이션 추가
- 새로운 애플리케이션 컨텍스트를 생성하여 다음 테스트에 영향을 주지 않음
- 그런데 애플리케이션 컨텍스트를 새로 생성하는 것도 찜찜함
테스트를 위한 별도의 DI 설정
테스트 코드에서 빈 오브젝트에 수동으로 DI하는 방법
- 코드가 많아져 번거로움
- 애플리케이션 컨텍스트를 매번 새로 만들어야 하는 부담
DataSource 클래스가 빈으로 정의된 테스트 전용 설정파일을 따로 만들어두는 방법
- 두가지 설정파일을 만들어 하나는 서버에서 운영용으로 사용할 DataSource를 빈으로 등록
- 다른 하나는 테스트용 DataSource를 빈으로 등록
- 테스트에서는 테스트 전용 설정파일을 사용
적용 방법
- applicationContext.xml을 복사하여 test-applicationContext.xml을 만듦
- 다른 빈설정은 그대로 두고 dataSource 빈만 테스트용으로 변경
- UserDaoTest의 ContextConfiguration 애노테이션의 location 엘리먼트 값을 변경
- SetUp 메소드의 수동 DI 코드와 DirtiesContext 애노테이션 제거
test-applicationContext.xml
<bean id="dataSource" class="org.springframework.jdbc.datasource.SimpleDriverDataSource">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@localhost:1521:xe"></property>
<property name="username" value="springbook_test"></property>
<property name="password" value="test"></property>
</bean>
UserDaoTest.java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("/test-applicationContext.xml")
public class UserDaoTest {
// ...
}
컨테이너 없는 DI 테스트
스프링 컨테이너를 사용하지 않고 테스트를 만드는 방법
- UserDao나 DataSource 구현 클래스에서 스프링 API를 직접 사용하거나 애플리케이션 컨텍스트를 이용하는 코드가 없으므로 스프링 DI 컨테이너에 의존하지 않아도 된다.
- 테스트 코드에서 직접 오브젝트를 만들고 DI해서 사용해도 된다.
- RunWith, Autowired를 사용하지 않고 직접 UserDao의 오브젝트를 생성하고 테스트용 Data 오브젝트를 만들어 직접 DI 해주면 된다.
UserDaoTest.java
public class UserDaoTest {
UserDao dao;
// ...
@Before
public void setUp() {
dao = new UserDao();
DataSource dataSource = new SingleConnectionDataSource("jdbc:oracle:thin:@localhost:1521:xe", "springbook_test", "test", true);
dao.setDataSource(dataSource);
}
}
DataSource를 직접 만들어야 하지만, 애플리케이션 컨텍스트를 아예 사용하지 않으므로 코드는 더 단순해지고 이해하기 편해짐
- 어플리케이션 컨텍스트가 만들어지지 않으므로 테스트 시간을 절약
- UserDao가 매번 새로 만들어지는 단점도 있음
DI를 이용한 테스트 방법 선택
DI를 테스트에 이용하는 방법
- 스프링 컨테이너 없이 테스트할 수 있는 방법
- 1순위로 고려
- 테스트 수행 속도가 가장 빠르고, 테스트가 간결하다.
- 스프링의 설정을 이용한 DI 방식의 테스트
- 여러 오브젝트와 복잡한 의존관계를 갖고 있는 오브젝트를 테스트해야 하는 경우
- 테스트 전용 설정 파일을 따로 만들어 사용
- 컨텍스트에서 DI 받은 오브젝트에서 다시 테스트 코드로 수동 DI 해서 테스트
- 테스트 설정을 따로 만들었지만, 예외적으로 의존관계를 강제적으로 테스트해야 하는 경우
- DirtiesContext 애노테이션을 반드시 붙여주어야 한다.