Spring팀은 전사적 소프트웨어 개발에서 완전히 필수부분이 되는 테스트를 생각한다.
이 장은 테스트에 관련된 부분이다. 전사적 소프트웨어 영역에서 테스트 처리를 통하는것은 이 장의 범위를 넘어선다. 간단하게는 IoC개념의 채택이 단위 테스트를 위해 가져올수 있는 값 추가에 집중하고 핵심적으로는 Spring프레임워크가 통합 테스트 영역에서 제공하는 명백한 이득에 집중한다.
의존성 삽입(Dependency Injection)의 주된 이익들 중 하나는 당신의 코드가 전통적인 J2EE 개발방법에 비해 컨테이너에 덜 의존적이라는 점이다. 당신의 어플리케이션을 구성하는 POJO는 Spring 혹은 어떠한 다른 컨테이너 없이 new연산자 실행을 사용하여 간단하게 초기화된 객체들을 가지고, JUnit 테스트에서 테스트 가능해야만 한다. 당신은 당신의 코드를 독립적으로 테스트하기 위해, 가짜(mock) 객체들 혹은 많은 다른 가치있는 테스팅 기법들을 사용할 수 있다. 만약 당신이 Spring을 둘러싼 구조적인 장점을 따른다면, 당신의 코드기반의 명백한 레이어화와 컴포넌트화가 테스팅을 좀더 쉽게 해준다는 사실을 알게될 것이다. 예를 들어, 당신은 단위 테스트 중에 퍼시스턴트 데이터에 접근할 필요없이, DAO 인터페이스들을 stub 혹은 mock 함으로써 서비스 계층 객체들을 테스트할 수 있을 것이다.
진정한 단위 테스트는 굉장히 빨리 실행될 것이다. 왜냐하면, 어플리케이션 서버, 데이터베이스, ORM 툴 등 셋업해야 할 실행시 하부구조가 아무것도 없기 때문이다. 따라서 진정한 단위 테스트는 당신의 생산성을 높혀줄 것이다. 이것의 결과는 당신이 Spring기반의 애플리케이션에 효과적인 단위 테스트를 작성하도록 도와주는 테스팅 장에서 이 부분은 필요하지 않다는 것이다.
그러나, 당신의 어플리케이션 서버에 대한 배치가 없거나 전사적 통합 시스템에 대한 실제 연결이 없이 몇몇 통합 테스팅을 실행할 수 있는 것은 매우 중요하다. 이것은 다음과 같은 테스트를 가능하게 할것이다.
당신의 Spring IoC컨테이너 컨텍스트들을 올바르게 연결하기
JDBC 혹은 ORM 툴을 사용한 데이터 접근. 이것은 정확한 SQL문이나 Hibernate XML맵핑 파일 설정과 같은 것들을 포함할것이다.
Spring은 통합 테스트를 위해 첫번째 클래스 지원을 제공한다. 첫번째 클래스 지원은 Spring배포판에 포함되어 있는 spring-mock.jar 내 많은 수의 클래스로 획득한다. 이 라이브러리내 클래스들은 Cactus와 같은 도구를 사용한 컨테이너 테스팅에 명백한 대안처럼 생각될수 있다.
![]() | Note |
---|---|
이 장의 나머지에서 언급된 test클래스의 모든것은 JUnit특성을 지닌다. |
org.springframework.test 패키지는 애플리케이션 서버나 다른 배치된 환경을 의지하지 않는 같은 시간동안 Spring 컨테이너를 사용하는 통합 테스팅을 위해 가치있는 수퍼클래스를 제공한다. 그런 테스트들은 다른 특별한 배포단계 없이 JUnit-심지어 IDE 내에서도--에서 실행될 수 있다. 그것들은 단위 테스트보다는 늦게 실행되겠지만, Cacus 테스트 또는 어플리케이션 서버 배포판에 의존적인 원격 테스트보다는 훨씬 빠를 것이다.
이 패키지내 다양한 추상 클래스는 다음의 기능들을 제공한다:
Spring IoC컨테이너는 테스트 케이스 수행간에 캐싱한다.
테스트 시설 자체의 의존성 삽입.
통합 테스팅에 적절한 트랜잭션 관리.
테스팅을 위해 유용한 상속된 인스턴스 변수.
2004년 말이래 많은 수의 Interface21와 다른 프로젝트들은 이러한 접근방식의 강력함과 유용함을 보여왔다. 기능에 있어서의 몇가지 중요한 부분들을 상세하게 살펴보도록 하자.
org.springframework.test 패키지는 Spring 컨텍스트의 일관된 로딩과 로딩된 컨텍스트의 캐슁을 제공한다. 후자는 매우 중요한데, 왜냐하면 만약 당신이 큰 프로젝트에서 일을하고 있다면, 시작 시간은 이슈가 될 것이기 때문이다. --이것은 Spring 그 자체의 오버헤드때문이 아니라 Spring 컨테이너에 의해 초기화되는 객체들이 그 자체로 초기화에 시간이 걸리기 때문이다. 예를 들어, 50-100개의 Hibernate 매핑 파일들을 가진 프로젝트는 언급한 맵핑파일들을 로드하는데 10-20초 가량 소요되고, 모든 한개 단위의 테스트 기법내 모든 한개 단위의 테스트 케이스가 전체 테스트보다 더 늦게 되는 것은 굉장한 생산성 감소를 초래하게 될 것이다.
이 이슈를 할당하기 위해, AbstractDependencyInjectionSpringContextTests는 컨텍스트들의 위치를 제공하기 위해 하위 클래스들이 반드시 구현해야 하는 abstract protected 메소드를 가진다.
protected abstract String[] getConfigLocations();
이 메소드의 구현물은 애플리케이션을 설정하기 위해 사용되는 XML설정 메타데이타의 자원 위치-대개 클래스패스인-를 가지는 배열을 제공해야만 한다. 이것은 web.xml이나 다른 배치 설정내 명시되는 설정 위치의 목록처럼 같거나 거의 같을것이다.
디폴트로, 한번 로드되면, 설정세트는 매 테스트 케이스를 위해 재사용될 것이다. 그래서, 셋업 비용은 단지 한번만 발생되고 뒤이은 테스트 실행은 보다 빠를 것이다.
테스트가 설정 위치를 지저분하게하는 정말 드문 경우, --예를 들어, 빈 정의 혹은 애플리케이션 객체의 상태를 수정 등으로 인해-- 리로딩을 요청하게 될 때, 당신은 AbstractDependencyInjectionSpringContextTests 에 있는 setDirty() 메소드를 호출할 수 있다. 이것은 다음 테스트 케이스를 실행하기 전에 설정을 리로드하고 어플리케이션 컨텍스트를 재생성하게 한다.
AbstractDependencyInjectionSpringContextTests(와 하위클래스들)가 당신의 어플리케이션 컨텍스트를 로드할 때, 그것들은 선택적으로 당신의 테스트 클래스들의 인스턴스들을 세터 주입을 통해 설정할 수 있다. 당신이 해야 할 일은 인스턴스 변수들과 그에 일치하는 세터들을 정의하는 것 뿐이다. AbstractDependencyInjectionSpringContextTests는 getConfigLocations() 메소드에서 기술된 설정 파일들의 세트에서 일치되는 객체들을 자동으로 위치시킬 것이다.
액션내 강력한 기능의 간단한 예제를 보자. Title도메인 객체를 말하기 위한 데이터 접근 로직을 수행하는 HibernateTitleDao 클래스를 가지는 시나리오를 생각해보자. 우리는 다음 영역모두를 테스트하는 통합 테스트를 작성하길 원한다.
Spring설정 : 이를테면, HibernateTitleDao에 관련된 모든것이 정확하고 존재하는가..?
Hibernate맵핑 파일 설정 : 이를테면 모든것이 정확하게 맵핑되고 대신 의미를 늦게 정확하게 로드하는가.?
HibernateTitleDao의 로직 : 이를테면 이 클래스가 예상하는것처럼 수행되는가.?
test클래스 자체를 보자.(우리는 부수적인 설정을 볼것이다.)
public class HibernateTitleDaoTests extends AbstractDependencyInjectionSpringContextTests { // this instance will be (automatically) dependency injected private HibernateTitleDao titleDao; // a setter method to enable DI of the 'titleDao' instance variable public void setTitleDao(HibernateTitleDao titleDao) { this.titleDao = titleDao; } public void testLoadTitle() throws Exception { Title title = this.titleDao.loadTitle(new Long(10)); assertNotNull(title); } // specifies the Spring configuration to load for this fixture protected String[] getConfigLocations() { return new String[] { "classpath:com/foo/daos.xml" }; } }
getConfigLocations()메소드에 의해 참조되는 부수적인 파일('classpath:com/foo/daos.xml')은 다음처럼 보일것이다.
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN 2.0//EN" "http://www.springframework.org/dtd/http://www.springframework.org/dtd/spring-beans-2.0.dtd"> <beans> <!-- this bean will be injected into the HibernateTitleDaoTests class --> <bean id="titleDao" class="com/foo/dao/hibernate/HibernateTitleDao"> <property name="sessionFactory" ref="sessionFactory"/> </bean> <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean"> <!-- dependencies elided for clarity --> </bean> </beans>
AbstractDependencyInjectionSpringContextTests는 타입에 의한 autowire를 사용한다(autowiring에 대한 정보는 Section 3.3.6, “Autowiring 협력자”에서 보라). 그래서 만약 당신이 동일한 타입의 빈 정의들을 여러개 가진다면, 당신은 특정한 빈들을 위해 이 접근방식을 사용할 수 없을 것이다. 이런 경우, 당신은 상속된 applicationContext 인스턴스 변수를 사용할 수 있으며, getBean()을 사용하여 명백하게 룩업할 수 있을 것이다.
만약 당신이 당신의 테스트 케이스들에 적용된 의존성 삽입을 원하지 않는다면, 간단히 setter들을 선언하지 말라. org.springframework.test 패키지의 클래스 구조의 가장 상위인, AbstractSpringContextTests를 확장할수 있다. 이 클래스는 단지 Spring 컨텍스트들을 로드하기 위해 편리한 메소드만을 가지고 있으며 테스트 기능의 어떤 의존성 삽입도 수행하지 않는다.
실제 데이터베이스에 접근하는 테스트에 있어 한 가지 일반적인 문제점은 데이터베이스 상태에 테스트가 영향을 끼친다는 점이다. 심지어 당신이 개발 데이터베이스를 사용할 때에도, 상태변화는 이후의 테스트들에 영향을 끼칠지 모른다. 또한, 데이터 삽입, 수정 등 많은 동작들은 트랜잭션 외부에서 이루어지거나 검증될 수 없을 것이다.
org.springframework.test.AbstractTransactionalDataSourceSpringContextTests상위 클래스(와 그 하위 클래스들)는 이러한 필요를 충적시키기 위해 존재한다. 기본적으로, 이 클래스들은 개별 테스트 케이스를 위해 트랜잭션을 생성하고 롤백한다. 당신은 간단하게 트랜잭션의 존재를 확인할 수 있는 코드를 쓰기만 하면 된다. 만약 당신이 트랜잭션적으로 프락시된 객체를 당신의 테스트 내에서 호출한다면, 그 객체들은 그들의 트랜잭션적인 문법에 따라 올바르게 동작할 것이다.
AbstractTransactionalSpringContextTests 는 어플리케이션 컨텍스트 내에 정의된 PlatformTransactionManager 빈에 의존한다. 타입에 의해 자동으로 묶어주기 때문에 이름은 중요한 것이 아니다.
일반적으로 당신은 하위 클래스인 AbstractTransactionalDataSourceSpringContextTests를 상속할 것이다. 이것은 또한 DataSource 빈 정의--이것 역시 아무 이름이어도 상관없다.--가 설정들 내에 존재해야만 한다. 이것은 편리한 질의를 위해 유용한 JdbcTemplate 인스턴스를 생성하고, 선택된 테이블들의 내용들을 삭제하기에 편리한 메소드들을 제공한다. (트랜잭션은 기본적으로 롤백된다는 점을 기억하라. 왜냐하면 그것이 안전하기 때문이다.)
만약 당신이 트랜잭션이 커밋되기를 원한다면,--드물지만, 예를 들어 만약 당신이 데이터베이스에 데이터를 입력시키는 특별한 테스트를 원한다면 유용할 것이다.-- 당신은 AbstractTransactionalSpringContextTests로부터 상속받은 setComplete() 메소드를 호출할 수 있다. 이것은 롤백 대신에 트랜잭션이 커밋되도록 할 것이다.
또한 테스트 케이스가 끝나기 전에 트랜잭션을 종료시키는 편리한 기능이 있는데, endTransaction() 메소드를 호출하면 된다. 이것은 기본적으로 트랜잭션을 롤백시키며, 오로지 이전에 setComplete() 가 호출되었을 때만 커밋시킨다. 이 기능은 만약 당신이 이를테면, 웹 혹은 트랜잭션 외부의 원격티어에서 사용될 Hibernate 매핑된 객체들과 같이, 연결이 끊어진 데이터 객체들의 동작을 테스트하고자 할 때 유용하다. 종종, lazy 로딩 에러는 UI 테스팅을 통해서만 발견되는데; 만약 당신이 endTransaction() 를 호출한다면, 당신은 JUnit 테스트 수트를 통해 UI의 올바른 동작을 검증할 수 있을 것이다.
이러한 테스트 지원 클래스들은 하나의 데이터베이스와 함께 동작하는 것으로 설계되었다.당신이 AbstractTransactionalDataSourceSpringContextTests 클래스를 확장할때, 당신은 다음의 protected 인스턴스 변수들에 접근할것이다.
applicationContext (ConfigurableApplicationContext): AbstractDependencyInjectionSpringContextTests 로부터 상속받았다. 명시적인 빈 룩업을 수행하거나 총괄적으로 컨텍스트의 상태를 테스트할 때 이것을 사용하라.
jdbcTemplate: AbstractTransactionalDataSourceSpringContextTests 로부터 상속받았다. 상태를 확인하기 위해 질의를 할 때 유용하다. 예를 들어, 당신이 객체를 생성하고, 그것을 ORM 툴을 사용하여 저장하는 어플리케이션 코드에 대한 테스팅 이전/이후에, 그 데이터가 데이터베이스에 나타나는지를 검증하기 위해 질의할 수 있을 것이다. (Spring은 그 쿼리가 동일 트랜잭션 범위에서 실행되는 것을 보증할 것이다.) 당신은 이것이 올바르게 작동되기 위해, ORM 툴이 그것의 변화들을 flush하도록 호출할 필요가 있을 것이다. 예를 들어, Hibernate Session 인터페이스의 flush() 메소드를 사용해서 말이다.
종종 당신은 통합 테스트를 위해 많은 테스트들에서 사용되는 보다 유용한 변수들을 제공하는 어플리케이션-포괄 상위 클래스를 제공할 것이다.
Spring 배포판에 포함된 PetClinic 샘플 어플리케이션은 이러한 테스트 상위 클래스들의 사용을 설명해준다.(Spring 1.1.5 이상 버전). 대부분의 테스트 기능은AbstractClinicTests에 포함되었고, 부분적인 내역들은 아래에서 보이는 대로이다.
public abstract class AbstractClinicTests extends AbstractTransactionalDataSourceSpringContextTests { protected Clinic clinic; public void setClinic(Clinic clinic) { this.clinic = clinic; } public void testGetVets() { Collection vets = this.clinic.getVets(); assertEquals('JDBC query must show the same number of vets', jdbcTemplate.queryForInt('SELECT COUNT(0) FROM VETS'), vets.size()); Vet v1 = (Vet) EntityUtils.getById(vets, Vet.class, 2); assertEquals('Leary', v1.getLastName()); assertEquals(1, v1.getNrOfSpecialties()); assertEquals('radiology', ((Specialty) v1.getSpecialties().get(0)).getName()); Vet v2 = (Vet) EntityUtils.getById(vets, Vet.class, 3); assertEquals('Douglas', v2.getLastName()); assertEquals(2, v2.getNrOfSpecialties()); assertEquals('dentistry', ((Specialty) v2.getSpecialties().get(0)).getName()); assertEquals('surgery', ((Specialty) v2.getSpecialties().get(1)).getName()); }
노트:
이 테스트 케이스는 이것은 의존성 삽입과 트랜잭션적인 동작을 위해 org.springframework.AbstractTransactionalDataSourceSpringContextTests 를 상속받았다.
clinic 인스턴스 변수--테스트될 어플리케이션 객체--는 setClinic(..) 메소드를 통해 의존성 삽입에 의해 세팅된다.
testGetVets() 메소드는 테스트될 어플리케이션 코드의 올바른 동작을 검증하기 위해 JdbcTemplate 변수를 사용하는 방법을 보여준다. 이것은 더욱 강력한 테스트들을 가능하게 하고 정확한 테스트 데이터에 대한 의존성을 줄여준다. 예를 들어, 당신은 테스트들을 중단하지 않고도 데이터베이스에 부가적인 row들을 추가할 수 있다.
데이터베이스를 사용하는 많은 통합 테스트들처럼, AbstractClinicTests 에서의 테스트들의 대부분은 테스트 케이스들이 실행되기 전 데이터베이스 내에 이미 존재하는 최소량의 데이터베이스에 의존한다. 그러나, 당신은 또한 당신의 테스트 케이스들 내에서 --물론, 하나의 트랜잭션 내에서-- 데이터베이스에 입력하는 것을 선택할 수도 있다.
PetClinic 어플리케이션은 4가지 데이터 접근 기술들을 제공한다.--JDBC, Hibernate, TopLink, 그리고 아파치 OJB. 그래서 AbstractClinicTests 는 컨텍스트 위치들을 기술하지 않는다. 이것은 AbstractDependencyInjectionSpringContextTests의 필수적인 protected abstract 메소드를 구현하는 하위 클래스들에 따라 다르다..
예를 들어, PetClinic 테스트의 Hibernate 구현은 다음의 메소드를 포함한다.
public class HibernateClinicTests extends AbstractClinicTests { protected String[] getConfigLocations() { return new String[] { '/org/springframework/samples/petclinic/hibernate/applicationContext-hibernate.xml' }; } }
PetClinic은 매우 간단한 어플리케이션이기 때문에, 단 하나의 Spring 설정 파일만이 존재한다. 물론, 보다 복잡한 어플리케이션들은 일반적으로 Spring 설정을 여러 개의 파일들로 쪼갤 것이다.
최하위 클래스에서 정의되는 대신, 설정 위치들은 모든 어플리케이션-특화 통합 테스트를 위한 일반적인 베이스 클래스에서 종종 기술된다. 이것은 또한 유용한 --자연스럽게 의존성 삽입에 의해 제공되는--Hibernate를 사용하는 어플리케이션의 경우의 HibernateTemplate와 같은인스턴스 변수들을 추가할 것이다.
가능한 한, 당신은 배포될 환경에서와 동일한 Spring 설정 파일들을 통합 테스트에서도 가져야만 한다. 유일하게 다른점이라면 데이터베이스 커넥션 풀링과 트랜잭션 하부구조와 관련된 부분들 뿐이다. 만약 당신이 고성능(? full-blown) 어플리케이션 서버에 배포할 것이라면, 당신은 아마도 그것의 (JNDI를 통해 가능한) 커넥션 풀과 JTA 구현을 사용할 것이다. 따라서 제품 내에서, 당신은 DataSource와 JtaTransactionManager를 위해 JndiObjectFactoryBean을 사용할 것이다. JNDI와 JTA는 컨테이너 외부 통합 테스트에서는 가능하지 않을 것이다. 따라서, 당신은 반드시 Commons DBCP BasicDataSource와 DataSourceTransactionManager 혹은 HibernateTransactionManager와 같은 조합을 사용해야 할 것이다. 당신은 어플리케이션 서버와 로컬 설정 사이의 선택을 가지는 이러한 다양한 동작들을, 테스트와 제품 환경들 사이에 변화가 없는 모든 다른 설정들로부터 분리하여, 하나의 XML 파일에 분해할 수 있다.
이 부분은 일반적으로 테스팅에 대한 더 많은 자원을 위한 링크를 포함한다.
Grinder 홈페이지 (로드테스팅 프레임워크).