[Info]Tags categorized posts and contents patterns..

[AJAX] Ajax Code E xamples.. [Book] About the book.. [CSS] CSS Code E xamples.. [DB] Sql Code E xamples.. [DEV] All development stor...

2016년 5월 20일 금요일

[Book] 자바 개발자를 위한 함수형 프로그래밍..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

자바 개발자를 위한 함수형 프로그래밍 - 6점
딘 왐플러 지음
임백준 옮김
한빛미디어

이 책은 한빛미디어에서 리얼타임이라는 이름으로 기술서적을 이북으로만 출판하는 서비스로 나온 책으로 Functional Programming for Java Developers의 번역서이다. 개인적으로는 페이퍼북으로 나오는 책에 비해 상당히 빠른 속도로 번역서가 나오고 있고 이북으로 기술서적을 보기 어려운 국내환경에서 볼만한 서적들이 이북으로 나오고 있기 때문에 최근에 맘에 들어하고 있다.

제목 그래로 자바의 관점(?)에서 함수형 프로그래밍을 설명해 주는 책이다. 이 책의 저지인 딘 왐플러는 오렐리의 Programming Scala의 저자이기도 한데 그래서 인지 몰라도 이 책을 다보고난 느낌은 스칼라의 내부 구현에 대한 접근방법에 대한 설명을 하는 듯한 느낌이었다. 스칼라의 내부 구현을 다 보여준다는 의미가 아니라 스칼라가 함수형 프로그래밍에 접근하고 있는 방법에 대해서 자바코드로 간략하게 보여주는 듯한 느낌이 있다. 자바 개발자들에게도 도움이 되겠지만 스칼라를 공부할 예정이라면 기반지식정도로 한번 보면 도움이 되리라 생각한다.

함수형 프로그래밍 언어는 객체지향 프로그래밍 언어를 대체하는 존재가 아니라, 객체지향 프로그래밍 언어의 외연을 확장하고 내연을 풍부하게 만들어 주는 도우미다.
함수형 프로그래밍은 개발자로써 배워둘 가치가 충분히 있다고 생각하는데 이는 기존의 임퍼러티브(Imperative) 프로그래밍에 비해서 함수형 프로그래밍은 접근방법이 완전히 다르기 때문에 코딩을 하는 좀더 넓은 시야를 준다고 보기 때문이다. 물론 이 100페이지정도의 책으로 함수형 프로그래밍의 개념을 다 이해하는 건 무리겠지만 기본적인 내용 정도는 이해할 수 있다고 생각한다. 개인적으로 번역은 약간 거친 느낌이 나지만 내용을 이해하는데는 무리가 없어 보인다.(Gang of four를 유명한 네명의 사람들이라고 한건 그냥 gang of four로 해도 괜찮지 않았나 싶기도... 중요한 건 아니지만...)

초반에는 함수형 프로그래밍에 대한 광고를 좀 하고 함수형 프로그래밍이 가진 특징들을 설명해 준다. 이어서 함수형 프로그래밍이 데이터 구조에 대해서 접근하는 방법을 설명하기 위해서 리스트와 맵에 대해서 설명하고 액터모델도 살짝 나온다. 데이터 구조에 대해서는 map, filter, fold를 자바코드로 구현하는 방법을 설명하고 이 세 함수를 조합해서 사용하는 방법을 보여주는데 이 책에서는 가장 알찬 부분이 아닌가 생각한다. 앞에서 말했듯이 100페이지밖에 안되므로 부담없이 읽을 수 있다.



[JAVA] 10장 테스트 #2..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

10.3.5.2 컨텍스트 관리
각 TestContext는 테스트 인스턴스에 대해 신뢰할 수 있는 컨텍스트 관리와 캐싱지원을 제공한다. 테스트 인스턴스는 설정된 ApplicationContext로의 접근이 자동으로 허용되지 않는다. 하지만 테스트 클래스가 ApplicationContextAware 인터페이스를 구현하면 ApplicationContext의 참조가 테스트 인스턴스에 제공된다. AbstractJUnit4SpringContextTests와 AbstractTestNGSpringContextTests가 ApplicationContextAware를 구현했으므로 ApplicationContext에 대한 접근을 제공한다.


@Autowired ApplicationContext
ApplicationContextAware 인터페이스를 구현하는 대신 필드나 setter 메서드에 @Autowired 어노테이션을 붙혀서 테스트 클래스의 어플리케이션 컨텍스트를 주입할 수 있다. 다음 예를 보자.

Java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class MyTest {

  @Autowired
  private ApplicationContext applicationContext;

  // class body...
}

@Autowired를 통한 의존성 주입을 기본적으로 설정된 DependencyInjectionTestExecutionListener가 제공한다.(Section 10.3.5.3, “테스트 픽스처의 의존성 주입”)를 참고해라.)

TestContext 프레임워크를 사용하는 테스트 클래스들은 어플리케이션 컨텍스트를 설정하기 위해 어떤 클래스도 상속받을 필요가 없고 특정 인터페이스를 구현할 필요도 없다. 대신 클래스 수준의 @ContextConfiguration 어노테이션을 선언함으로써 설정이 이뤄진다. 테스트 클래스가 어플리케이션 컨텍스트 리소스 locations이나 설정 classes를 명시적으로 선언하지 않았다면 설정된 ContextLoader가 기본 위치나 기본 설정 클래스에서 어떻게 컨텍스트를 로드할 지 결정한다.

다음 섹션에서는 ApplicationContext를 XML 설정 파일이나 스프링의 @ContextConfiguration 어노테이션을 사용한 @Configuration 클래스로 어떻게 설정하는지 설명한다.

XML 리소스를 사용한 컨텍스트 설정
XML 설정파일로 테스트의 ApplicationContext를 로딩하려면 테스트 클래스에 @ContextConfiguration 어노테이션을 붙히고 XML 설정 메타데이터에 리소스 위치를 담고 있는 locations 속성을 가진 배열을 설정해라. 이 평범한(plain) 경로(예를 들면 "context.xml"같은)는 테스트 클래스를 정의한 패키지의 상대적인 클래스패스 리소스로 다뤄질 것이다. 슬래시로 시작하는 경로는 절대경로 클래스패스 위치로 다룬다. 예를 들어 "/org/example/config.xml"같은 것이다. 리소스 URL을 나타내는 경로(classpath:, file:, http: 등의 접두사가 붙은 경로)는 원래 그대로 사용될 것이다. 대신에 자신만의 커스텀 ContextLoader나 고급 사용을 위해 SmartContextLoader를 구현하거나 설정할 수 있다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// 어플리케이션 컨텍스트는 클래스패스의 루트의 "/app-config.xml"와
// "/test-config.xml"에서 로드될 것이다.
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
public class MyTest {
  // class body...
}

@ContextConfiguration 는 표준 자바 value 속성으로 locations 속성의 별칭을 지원한다. 그러므로 커스텀 ContextLoader를 설정할 필요가 없다면 locations 속성명의 선언을 생략하고 다음 예제에 나오는 것처럼 약식으로 리소스 위치를 선언할 수 있다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
public class MyTest {
  // class body...
}

@ContextConfiguration 어노테이션의 locations와 value 속성을 모두 생략하면 TestContext 프레임워크가 기본 XML 리소스 위치를 찾으려고 시도할 것이다. 특히, GenericXmlContextLoader는 테스트 클래스 이름에 기반해서 기본 위치를 찾는다. 클래스 이름이 com.example.MyTest이라면 GenericXmlContextLoader는 "classpath:/com/example/MyTest-context.xml"에서 어플리케이션 컨텍스트를 로드한다.

Java

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 "classpath:/com/example/MyTest-context.xml"에서 로드될 것이다.
@ContextConfiguration
public class MyTest {
  // class body...
}

@Configuration 클래스를 사용한 컨텍스트 설정
@Configuration 클래스(Section 4.12, “자바기반의 컨테이너 설정” 참고)를 사용해서 테크트의 ApplicationContext를 로드하려면 테스트 클래스에 @ContextConfiguration 어노테이션을 붙히고 설정 클래스에 대한 찹조를 답고 있는 classes 속성의 배열을 설정해라. 아니면 자신만의 커스텀 ContextLoader나 고급 사용을 위한 SmartContextLoader를 구현하거나 설정할 수 있다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 AppConfig와 TestConfig에서 로드될 것이다
@ContextConfiguration(classes={AppConfig.class, TestConfig.class})
public class MyTest {
  // class body...
}

@ContextConfiguration 어노테이션에서 classes 속성을 생략하면 TestContext 프레임워크는 기본 설정 클래스의 존재를 찾으려고 시도할 것이다. 특히, AnnotationConfigContextLoader는 @Configuration Javadoc에 나온 것처럼 설정 클래스 구현체의 요구사항을 만족시키는 어노테이션이 붙은 테스트 클래스의 정적 이너클래스를 모두 찾을 것이다. 다음 예제에서 OrderServiceTest 클래스는 Config라는 정적 이너 설정 클래스를 선언했고 테스트 클래스를 위한 ApplicationContext를 로드하는데 자동으로 사용할 것이다. 설정 클래스의 이름은 임의로 사용할 수 있다. 추가적으로 필요하다면 테스트 클래스는 하나이상의 정적 이너 설정클래스를 포함할 수 있다.

Java

package com.example;
 
@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 정적 이너 Config 크래스에서 로드될 것이다.
@ContextConfiguration
public class OrderServiceTest {
 
  @Configuration
  static class Config {

    // 이 빈은 OrderServiceTest 클래스로 주입될 것이다
    @Bean
    public OrderService orderService() {
      OrderService orderService = new OrderServiceImpl();
      // set 프로퍼티 등등.
      return orderService;
    }
  }

  @Autowired
  private OrderService orderService;

  @Test
  public void testOrderService() {
    // orderService를 테스트한다
  }
}

XML 리소스와 @Configuration 클래스를 섞어서 사용하기
때 로는 테스트의 ApplicationContext를 설정할 때 XML 리소스와 @Configuration 클래스를 섞어서 사용하기를 원할 수 있다. 예를 들어 프로덕션 레벨에서는 XML 설정을 사용하고 테스트의 스프링이 관리하는 특정 컴포넌트를 설정할 때는 @Configuration 클래스를 사용하고자 하거나 그 반대로 사용하는 경우이다. Section 10.3.4.1, “Spring Testing Annotations”에서 얘기했듯이 TestContext 프레임워크는 @ContextConfiguration로 두가지 모두를 선언하게 하지는 않지만 이것이 둘다 사용할 수 없다는 의미는 아니다.

테스트 설정에 XML과 @Configuration 클래스를 사용하려면 진입점으로 하나를 선택하고 진입점이 다른 것을 포함하거나 임포트해야한다. 예를 들어 XML에 컴포넌트 스캔이나 평범한 스프링 빈으로 @Configuration 클래스를 정의해서 @Configuration 클래스들을 포함시킬 수 있다. 반면에, @Configuration 클래스에서는 XML 설정파일을 임포트하기 위해 @ImportResource를 사용할 수 있다. 이 동작은 프로덕션에서 어플리케이션을 구성하는 방법과 의미적으로 동일하다. 프로덕션 설정에서 프로덕션 ApplicationContext가 로딩될 XML 리소스 위치의 세트나 @Configuration 클래스의 세트를 정의할 것이지만 여전히 다른 종류의 설정을 자유롭게 포함하거나 임포트할 수 있다.

컨텍스트 설정의 상속
@ContextConfiguration 은 상속해야 하는 수퍼클래스가 선언한 리소스 위치나 설정 클래스를 나타내는 inheritLocations 불리언 속성을 지원한다. 기본값은 true이다. 즉 어노테이션이 붙은 클래스는 어노테이션이 붙은 수퍼클래스가 선언한 리소스 위치나 설정 클래스를 상속받는다. 특히, 어노테이션이 붙은 테스트 클래스를 위한 리소스 위치나 설정 클래스들은 어노테이션이 붙은 수퍼클래스가 선언한 리소스 위치나 설정클래스의 목록에 추가된다. 그러므로 서브클래스들은 리소스 위치나 설정 클래스의 목록을 확장하는 옵션을 가진다.

@ContextConfiguration 의 inheritLocations 속성을 false로 설정하면 어노테이션이 붙은 클래스의 리소스 위치나 설정 클래스들은 가려지고(shadow) 효과적으로 수퍼클래스가 정의한 리소스 위치나 설정 클래스를 교체한다.

XML 리소스 위치를 사용하는 다음의 예제에서 ExtendedTest의 ApplicationContext는 "base-config.xml"와 "extended-config.xml"에서 그 순서대로 로드될 것이다. 그러므로 "extended-config.xml"에서 정의한 빈은 "base-config.xml"에서 정의한 빈으로 오버라이드(또는 교체) 될 것이다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 클래스패스 루트의 "/base-config.xml"에서 로드될 것이다.
@ContextConfiguration("/base-config.xml")
public class BaseTest {
  // class body...
}

// ApplicationContext는 클래스 패스 루트의 "/base-config.xml"와 "/extended-config.xml"에서 
// 로드될 것이다
@ContextConfiguration("/extended-config.xml")
public class ExtendedTest extends BaseTest {
  // class body...
}

비 슷하게 설정 클래스를 사용하는 다음 예제는 ExtendedTest의 ApplicationContext는 BaseConfig 와 ExtendedConfig 설정 클래스에서 그 순서대로 로드된다. 그러므로 ExtendedConfig에서 정의한 빈은 BaseConfig에서 정의한 빈으로 오버라이드(교체)된다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 BaseConfig에서 로드될 것이다
@ContextConfiguration(classes=BaseConfig.class)
public class BaseTest {
  // class body...
}

// ApplicationContext는 BaseConfig와 ExtendedConfig에서 로드될 것이다
@ContextConfiguration(classes=ExtendedConfig.class)
public class ExtendedTest extends BaseTest {
  // class body...
}

환경 프로파일을 사용하는 컨텍스트 설정
스 프링 3.1은 환경과 프로파일(또는 빈 정의 프로파일(bean definition profiles))의 개념에 대해 프레임워크수준의 최고급 지원을 도입했고 통합 테스트는 이제 다양한 테스트 시나리오에 따라 특정 빈 정의 프로파일을 활성화하도록 설정될 수 있다. 이는 테스트 클래스에 새로운 @ActiveProfiles 어노테이션을 붙히고 테스트의 ApplicationContext를 로드할 때 활성화 되어야 하는 프로파일의 목록을 제공함으로써 이룰 수 있다.


Note
@ActiveProfiles는 새로운 SmartContextLoader SPI의 어떤 구현체와도 사용할 수 있지만 @ActiveProfiles는 구식 ContextLoader SPI의 구현체는 지원하지 않는다.

XML 설정과 @Configuration 클래스를 가진 몇가지 예제를 살펴보자.

Xml

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xmlns:jdbc="http://www.springframework.org/schema/jdbc"
  xmlns:jee="http://www.springframework.org/schema/jee"
  xsi:schemaLocation="...">

  <bean id="transferService" class="com.bank.service.internal.DefaultTransferService">
    <constructor-arg ref="accountRepository"/>
    <constructor-arg ref="feePolicy"/>
  </bean>

  <bean id="accountRepository" class="com.bank.repository.internal.JdbcAccountRepository">
    <constructor-arg ref="dataSource"/>
  </bean>

  <bean id="feePolicy" class="com.bank.service.internal.ZeroFeePolicy"/>

  <beans profile="dev">
    <jdbc:embedded-database id="dataSource">
      <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/>
      <jdbc:script location="classpath:com/bank/config/sql/test-data.sql"/>
    </jdbc:embedded-database>
  </beans>

  <beans profile="production">
    <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
  </beans>
</beans>


Java

package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext는 "classpath:/app-config.xml"에서 로드된다
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {

  @Autowired
  private TransferService transferService;

  @Test
  public void testTransferService() {
    // transferService를 테스트한다
  }
}

TransferServiceTest 를 실행할 때 TransferServiceTest의 ApplicationContext는 클래스패스 루트의 app-config.xml 설정 파일에서 로드될 것이다. app-config.xml를 검사하면 accountRepository 빈이 dataSource 빈에 의존성을 가진다는 것을 알아차릴 것이다. 하지만 dataSource는 최상위 빈으로 정의되지 않았다. 대신, dataSource는 두번 정의됐다. 한번은 production 프로파일이고 한번은 dev 프로파일이다.

TransferServiceTest 에 @ActiveProfiles("dev") 어노테이션을 붙혀서 스프링 TestContext 프레임워크가 현재 프로파일을 {"dev"}로 설정해서 ApplicationContext를 로드하도록 한다. 그 결과 내장된 데이터베이스를 생성하고 accountRepository 빈을 개발 DataSource에 대한 참조와 연결할 것이다. 이것이 통합테스트에서 하려고 하는 것이다.

다음 코드 목록은 XML 대신 @Configuration 클래스를 사용해서 어떻게 같은 설정과 통합테스트를 구현하는지 보여준다.

Java

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

  @Bean
  public DataSource dataSource() {
    return new EmbeddedDatabaseBuilder()
        .setType(EmbeddedDatabaseType.HSQL)
        .addScript("classpath:com/bank/config/sql/schema.sql")
        .addScript("classpath:com/bank/config/sql/test-data.sql")
        .build();
  }
}


Java

@Configuration
@Profile("production")
public class JndiDataConfig {

  @Bean
  public DataSource dataSource() throws Exception {
    Context ctx = new InitialContext();
    return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
  }
}


Java

@Configuration
public class TransferServiceConfig {

  @Autowired DataSource dataSource;

  @Bean
  public TransferService transferService() {
    return new DefaultTransferService(accountRepository(), feePolicy());
  }

  @Bean
  public AccountRepository accountRepository() {
    return new JdbcAccountRepository(dataSource);
  }

  @Bean
  public FeePolicy feePolicy() {
    return new ZeroFeePolicy();
  }
}


Java

package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(
  classes={
    TransferServiceConfig.class,
    StandaloneDataConfig.class,
    JndiDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {

  @Autowired
  private TransferService transferService;

  @Test
  public void testTransferService() {
    // transferService를 테스트한다
  }
}

이번 변경에서는 XML 설정을 3개의 독립적인 @Configuration 클래스로 분리했다.

TransferServiceConfig: @Autowired를 사용한 의존성 주입으로 dataSource를 얻는다.
StandaloneDataConfig: 개발자 테스트에 적합한 내장 데이터베이스의 dataSource를 정의한다.
JndiDataConfig: 프로덕션 환경의 JNDI에서 획득한 dataSource를 정의한다.

XML 에 기반한 설정 예제처럼 TransferServiceTest에 여전히 @ActiveProfiles("dev") 어노테이션을 붙혔다. 하지만 이번에는 @ContextConfiguration 어노테이션으로 3개의 설정 클래스를 모두 지정했다. 테스트 클래스 자체의 바디는 전혀 변경하지 않고 놔두었다.

컨텍스트 캐싱
TestContext 프레임워크가 테스트에 대한 ApplicationContext를 일단 로드하면 이 컨텍스트는 캐싱될 것이고 같은 테스트슈트(test suite)내에서 유일한(unique) 컨텍스트 설정을 같이 선언한 이어진 모든 테스트에서 재사용할 것이다. 캐싱이 어떻게 동작하는지 이해하려면 유일하다(unique)와 테스트슈트(test suite)의 의미를 이해하는 것이 중요하다.

ApplicationContext 를 어플리케이션 컨텍스트를 로드할 때 사용한 설정 파라미터들의 조합으로 유일하게 구분할 수 있다. 따라서 설정 파라미터들의 유일한 조합을 캐싱된 컨텍스트의 키를 생성하는데 사용한다. TestContext 프레임워크는 컨텍스트 캐시 키를 구성할 때 다음의 설정 파라미터들을 사용한다.

  • locations ( @ContextConfiguration에서)
  • classes ( @ContextConfiguration에서)
  • contextLoader ( @ContextConfiguration에서)
  • activeProfiles ( @ActiveProfiles에서)
예 를 들어, TestClassA가 @ContextConfiguration의 locations (또는 value) 속성에 {"app-config.xml", "test-config.xml"}를 지정했다면 TestContext 프레임워크가 대응되는 ApplicationContext를 로드하고 이러한 위치(location)에 단독으로 기반하는 키 아래있는 static 컨텍스트 캐시에 이 ApplicationContext를 저장한다. 그래서 TestClassB도 위치(location)에 {"app-config.xml", "test-config.xml"}를 정의하고 다른 ContextLoader나 다른 활성화된 프로파일을 정의하지 않았다면 두 테스트 클래스가 같은 ApplicationContext를 공유할 것이다. 즉, 어플리케이션 컨텍스트를 로딩하는 설정 비용은 딱 한번만 발생하고(테스트슈트마다) 이어지는 테스트 실행은 훨씬 더 빨라진다.

테스트 슈트와 포크된(forked) 프로세스
스 프링 TestContext 프레임워크는 static 캐시에 어플리케이션 컨텍스트를 저장한다. 이 말은 컨텍스트가 static 변수에 리터럴하게 저자오딘다는 의미이다. 다시 말하자면 분리된 프로세스에서 테스트를 실행할 때 정적 캐시는 각 테스트 실행간에 비워질 것이고 이는 효율적으로 캐싱 메카니즘을 사용안하도록 한다.

캐싱 메카니즘의 이득을 얻으려면 모든 테스트가 같은 프로세스나 같은 테스트슈트에서 돌아가야 한다. 이는 IDE에서 하나의 그룹으로 모든 테스트를 실행해서 이룰 수 있다. 유사하게 Ant나 Maven같은 빌드 프레임워크로 테스트를 실행할 때 빌드 프레임워크가 테스트들 사이에서 포크(fork)하지 않도록 하는 것이 중요하다. 예를 들어 Maven Surefire 플러그인에서 forkMode를 always나 pertest로 설정하면 TestContext 프레임워크는 테스트 클래스들 사이에서 어플리케이션 컨텍스트를 캐시할 수 없게 되어 빌드 과정이 현저하게 느려질 것이다.

테스트가 어플리케이션 컨텍스트를 망가뜨려서 리로딩을 해야 하는 흔치않은 경우에(예를 들어 빈 정의나 어플리케이션 객체의 상태를 수정해서) 테스트 클래스나 테스트 메서드에 @DirtiesContext 어노테이션을 붙힐 수 있다. (Section 10.3.4.1, “Spring Testing Annotations”에서 @DirtiesContext에 관한 논의를 참고해라.) 이 어노테이션은 스프링이 캐시에서 컨텍스트를 제거하고 다음 테스트를 실행하기 전에 어플리케이션 컨텍스트를 다시 구성하도록 지시한다. @DirtiesContext에 대한 지원은 기본적으로 사용가능한 DirtiesContextTestExecutionListener에서 제공한다.

10.3.5.3 테스트 픽스처의 의존성 주입
기 본적으로 설정되는 DependencyInjectionTestExecutionListener를 사용할 때 테스트 인스턴스의 의존성을 @ContextConfiguration로 설정한 어플리케이션 컨텍스트의 빈에서 주입한다. setter 메서드나 필드에 어떤 어노테이션을 붙히는 지에 따라 setter 주입이나 필드 주입, 또는 둘다를 사용할 것이다. 스프링 2.5와 3.0에서 도입된 어노테이션 지원의 일관성을 위해서 스프링의 @Autowired 어노테이션이나 JSR 330의 @Inject를 사용할 수 있다.


Tip
TestContext 프레임워크는 테스트 인스턴스를 인스턴스하는 방법으로 구성하지 않는다. 그러므로 생성자에 @Autowired나 @Inject를 사용해서 테스트 클래스에는 효과가 없다.

@Autowired 는 타입으로 자동연결하기 때문에 같은 타입의 빈 정의를 여러 개 가지고 있다면 이러한 빈들에는 이 접근을 사용할 수 없다. 이러한 경우에는 @Qualifier와 @Autowired를 함께 사용할 수 있다. 스프링 3.0에서는 @Inject를 @Named와 함께 사용할 수도 있다. 아니면 테스트 클래스가 자신의 ApplicationContext에 접근한다면 (예를 들어) applicationContext.getBean("titleRepository")를 호출해서 명시적으로 검색할 수 있다.

테 스트 인스턴스에 의존성 주입을 하고 싶지 않다면 그냥 필드나 setter 메서드에 @Autowired나 @Inject 어노테이션을 붙히지 말아라. 아니면 명시적으로 클래스를 @TestExecutionListeners로 설정하고 리스너 목록에서 DependencyInjectionTestExecutionListener.class를 생략해서 의존성 주입은 전혀 사용하지 않게 할 수 있다.

목표 섹션에서 간략히 설명했듯이 HibernateTitleRepository 클래스를 테스트하는 시나리오를 생각해 보자. 다음의 두 코드는 필드와 setter 메서드에 @Autowired를 사용하는 방법을 보여준다. 어플리케이션 컨텍스트 설정은 모든 샘플코드 뒤에 나와있다.


Note
다음 코드목록에서 의존성 주입을 JUnit으로 지정하지 않았다. 동일한 DI 기술을 어떤 테스트 프레임워크와도 사용할 수 있다.

다 음의 예제들은 Assert를 앞에 붙히지 않고 assertNotNull()같은 정적 단언문 메서드를 호출한다. 이러한 호출은 예제에는 나와있지 않지만 import static 선언으로 해당 메서드들이 제대로 임포트되었다고 가정한 것이다.

첫 예제코드는 필드 주입으로 @Autowired를 사용한 테스트 클래스의 JUnit 기반 구현체를 보여준다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// 이 테스트 픽스처를 로드하도록 스프링 설정을 지정한다
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

  // 이 인스턴스는 타입으로 
  의존성 주입될 것이다.
  @Autowired    
  private HibernateTitleRepository titleRepository;

  @Test
  public void findById() {
    Title title = titleRepository.findById(new Long(10));
    assertNotNull(title);
  }
}

아니면 아래에서 보듯이 setter 주입으로 @Autowired를 사용하도록 클래스를 설정할 수 있다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
// 이 테스트 픽스처를 로드하도록 스프링 설정을 지정한다
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

  // 이 인스턴스는 타입으로 
  의존성 주입될 것이다.
  private HibernateTitleRepository titleRepository;

  @Autowired
  public void setTitleRepository(HibernateTitleRepository titleRepository) {
    this.titleRepository = titleRepository;
  }

  @Test
  public void findById() {
    Title title = titleRepository.findById(new Long(10));
    assertNotNull(title);
  }
}

앞의 코드들은 @ContextConfiguration 어노테이션으로 참조되는 다음과 같은 같은 XML 컨텍스트 파일을 사용한다.(즉 repository-config.xml이다.)

Xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" 
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

  <!-- 이 빈은 HibernateTitleRepositoryTests 클래스로 주입된다 -->
  <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
    <property name="sessionFactory" ref="sessionFactory"/>
  </bean>
  
  <bean id="sessionFactory" class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
      <!-- 간결함을 위해 설정을 생략했다 -->
  </bean>
</beans>


Note
setter 메서드에 @Autowired를 사용해서 스프링이 제공하는 테스트 기반 클래스를 확장한다면 영향을 받는 어플리케이션 컨텍스트에 정의한 타입의 여러가지 빈을 가질 것이다. 예를 들어 여러 DataSource 빈이다. 이러한 경우 setter 메서드를 오버라이드 할 수 있고 다음과 같이 지정한 대상 빈을 나타내기 위해 @Qualifier 어노테이션을 사용할 수 있지만 수퍼클래스의 오버라이드된 메서드에 위임하도록 해야한다.

Java

// ...

  @Autowired
  @Override
  public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
    super.setDataSource(dataSource);
  }

// ...

DataSource 빈을 나타내서 지정한 빈과 일치하는 타입의 세트로 제한한다. 제한자의 값은 대응되는 <bean> 정의내의 <qualifier> 선언과 일치한다. 빈 이름을 제한자 값의 대비책(fallback)으로 사용하므로 효과적으로 이름으로 특정빈을 가르킬 수도 있다.(위에서 보았듯이 "myDataSource"가 빈 id라고 가정한다.)

10.3.5.4 트랜잭션 관리
TestContext 프레임워크에서 트랜잭션은 TransactionalTestExecutionListener가 관리한다. 테스트 클래스에 명시적으로 @TestExecutionListeners를 선언하지 않더라도 TransactionalTestExecutionListener은 기본적으로 설정된다. 하지만 트랜잭션 지원을 활성화하려면 @ContextConfiguration의 의미로 로드하는 어플리케이션 컨텍스트에 PlatformTransactionManager를 제공해야 한다. 추가적으로 테스크 클래스에 클래스 수준 혹은 메서드 수준 @Transactional를 선언해야 한다.

클래스 수준의 트랜잭션 설정은(예시. 트랜잭션 관리자와 기본 롤백 플래그에 빈 이름을 설정) 어노테이션 지원 섹션의 @TransactionConfiguration 부분을 봐라.

트 랜잭션을 테스트 클래스 전체에 적용하지 않으면 메서드에 명시적으로 @Transactional 어노테이션을 붙힐 수 없다. 특정 테스트 메서드는 커밋이 되도록 트랜잭션을 제어하려면 클래스 수준의 기본 롤백 설정을 오버라이드하도록 @Rollback 어노테이션을 사용할 수 있다.

클래스 수준의 트랜잭션 지원을 위해서 AbstractTransactionalJUnit4SpringContextTests 와 AbstractTransactionalTestNGSpringContextTests가 미리 설정된다.

때 때로 트랜잭션이 적용된 컨텍스트 밖에서 트랜잭션이 적용된 테스트 메서드 이전이나 이후에 특정 코드를 실행해야할 필요가 있다. 예를 들면 테스트 실행에 앞서 초기 데이터베이스의 상태를 확인하거나 테스트 실행 이후에 예상한 트랜잭션 커밋 작업(테스트가 트랜잭션을 롤백하도록 설정하지 않았다면)을 확인하는 등이다. TransactionalTestExecutionListener는 정확히 이러한 시나리오를 위해서 @BeforeTransaction와 @AfterTransaction를 지원한다. 테스트 클래스의 어떤 public void 메서드라도 그냥 이러한 어노테이션 중에 하나를 붙히면 TransactionalTestExecutionListener가 절적한 시기에 before transaction method나 after transaction method의 실행을 보장한다.


Tip
모 든 before methods(JUnit의 @Before 어노테이션이 붙은 메서드처럼)와 after methods(JUnit의 @After 어노테이션이 붙은 메서드처럼)는 트랜잭션내에서 실행된다. 추가적으로 @BeforeTransaction나 @AfterTransaction 어노테이션이 붙은 메서드들은 @NotTransactional 어노테이션이 붙은 테스트에는 자연스럽게 실행하지 않는다. 하지만 @NotTransactional는 스프링 3.0부터는 폐기되었다.

다음의 JUnit 기반 예제는 트랜잭션과 관련된 여러 어노테이션을 강조하는 가상의 통합 테스트 시나리오를 보여준다. 더 자세한 내용과 설정 예제는 어노테이션 지원 섹션을 참고해라.

Java

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
@Transactional
public class FictitiousTransactionalTest {

  @BeforeTransaction
  public void verifyInitialDatabaseState() {
    // 트랜잭셩을 시작하기 전에 초기상태를 검증하는 로직
  }

  @Before
  public void setUpTestDataWithinTransaction() {
    // 트랜잭션내에서 테스트 데이터 구성
  }

  @Test
  // 클래스수준의 defaultRollback 설정을 오버라이드한다
  @Rollback(true)
  public void modifyDatabaseWithinTransaction() {
    // 테스트 데이터를 사용하고 데이터베이스의 상태를 수정하는 로직
  }

  @After
  public void tearDownWithinTransaction() {
    // 트랜잭션내의 "tear down" 로직을 실행한다
  }

  @AfterTransaction
  public void verifyFinalDatabaseState() {
    // 트랜잭션이 롤백된 후에 최종 상태를 검증하는 로직
  }
}


ORM 코드를 테스트할 때 false positive 피하기
하 이버네이트 세션의 상태를 조작하는 어플리케이션 코드를 테스트할 때 해당 코드를 실행하는 테스트 메서드내에서 의존하는 세션을 플러시(flush)하도록 해야한다. 기반 세션을 플러시하는 것이 실패하면 false positive (역주: 오류가 아닌데 오류라고 판단하는 경우를 의미한다)가 된다. 테스트는 통과할 것이지만 동일한 코드가 프로덕션 환경에서는 예외를 던질 것이다. 다음의 하이버네이트 기반 예제 테스트 케이스에서 한 메서드는 false positive를 보여주고 다른 메서드는 세션 플러시의 결과를 제대로 노출한다. 이는 내장 메모리(in-memory) 작업의 단위를 유지하는 JPA와 다른 ORM 프레임워크에도 적용된다.

Java

// ...

@Autowired
private SessionFactory sessionFactory;

@Test // 예상되는 예외는 없다!
public void falsePositive() {
  updateEntityInHibernateSession();
  // False positive: 예외가 던져지고 세션을 결국 플러시 될 것이다.
  // (예: 프로덕션 모드에서)
}

@Test(expected = GenericJDBCException.class)
public void updateWithSessionFlush() {
  updateEntityInHibernateSession();
  // 테스트에서 false positive를 피하려면 수동으로 플러시해야 한다
  sessionFactory.getCurrentSession().flush();
}

// ..


10.3.5.5 TestContext 지원 클래스
JUnit 지원 클래스
org.springframework.test.context.junit4 패키지는 JUnit 4.5+ 기반 테스트 케이스를 지원하는 클래스를 제공한다.

  • AbstractJUnit4SpringContextTests: JUnit 4.5+ 환경에서 명백한 ApplicationContext 테스트 지원과 Spring TestContext Framework와 통합된 추상 기반(base) 테스트 클래스

    AbstractJUnit4SpringContextTests를 확장할 때 다음의 protected 인스턴스 변수에 접근할 수 있다.
    • applicationContext: 명시적인 빈 검색을 수행하거나 컨텍스트의 상태를 전체적으로 테스트할 때 이 변수를 사용해라.
  • AbstractTransactionalJUnit4SpringContextTests: JDBC 접근에 다소 편리한 기능도 추가하는 AbstractJUnit4SpringContextTests의 추상 transactional 확장. javax.sql.DataSource 빈과 PlatformTransactionManager이 ApplicationContext에 정의될 것이다. AbstractTransactionalJUnit4SpringContextTests를 확장할 때 다음의 protected 인스턴스 변수에 접근할 수 있다.
    • applicationContext: AbstractJUnit4SpringContextTests 수퍼클래스를 상속받았다. 명시적인 빈 검색을 수행하거나 컨텍스트의 상태를 전체적으로 테스트할 때 이 변수를 사용해라.
    • simpleJdbcTemplate: 디비 쿼리를 하는 SQL 문을 실행할 때 이 변수를 사용해라. 이러한 쿼리는 데이터베이스와 관련된 어플리케이션 코드를 실행하기 이전과 이후 모두에 데이터베이스 상태를 확인하는데 사용할 수 있고 스프링이 이러한 쿼리가 어플리케이션 코드와 같은 트랜잭션의 범위로 실행된다는 것을 보장해준다. ORM 도구와 함께 사용할 때는 false positives를 피해야 한다.
Tip
이 러한 클래스들은 확장하기 쉽다. 스프링에 특화된 클래스 계층(예를 들어 테스트하는 클래스를 직접 확장하고자 한다면)에 묶인 테스트 클래스를 원하지 않는다면 @RunWith(SpringJUnit4ClassRunner.class), @ContextConfiguration, @TestExecutionListeners 등을 사용해서 자신만의 커스텀 테스트 클래스를 설정할 수 있다.

Spring JUnit Runner
Spring TestContext Framework는 커스텀 러너 (JUnit 4.5 ? 4.9에서 테스트되었다)로 JUnit 4.5+와 완전한 통합을 제공한다. 개발자들이 테스트 클래스에 @RunWith(SpringJUnit4ClassRunner.class) 어노테이션을 붙혀서 JUnit에 기반한 유닛테스트와 통합테스트를 구현할 수 있고 동시에 어플리케이션 컨텍스트 로딩, 테스트 인스턴스의 의존성 주입, 테스트 메서드 실행의 트랜잭션 적용 등의 TestContext 프레임워크의 이점을 얻을 수 있다. 다음의 코드 목록은 테스트 클래스를 커스텀 스프링 러너로 실행하도록 설정하기 위한 최소 요구사항을 보여준다. @TestExecutionListeners는 기본 리스너를 사용안하도록 비어있는 리스트로 설정했다.비어있는 리스트로 설정하지 않으면 ApplicationContext를 @ContextConfiguration로 설정해야 한다.

Java

@RunWith(SpringJUnit4ClassRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

  @Test
  public void testMethod() {
    // 테스트 로직 실행...
  }
}

TestNG 지원 클래스
org.springframework.test.context.testng 패키지는 TestNG 기반의 테스트 클래스를 지원하는 클래스를 제공한다.

  • AbstractTestNGSpringContextTests: TestNG 환경에서 명시적인 ApplicationContext 테스트 지원과 함께 Spring TestContext Framework와 통합하는 추상 기반(base) 테스트 클래스.

    AbstractTestNGSpringContextTests를 확장할 때 다음의 protected 인스턴스 변수에 접근할 수 있다.
    • applicationContext: 명시적인 빈 검색을 수행하거나 컨텍스트의 상태를 전체적으로 테스트할 때 이 변수를 사용해라.
  • AbstractTransactionalTestNGSpringContextTests: JDBC 접근을 위한 다소 편리한 기능을 추가한 AbstractTestNGSpringContextTests의 추상 transactional 확장이다. javax.sql.DataSource 빈과 PlatformTransactionManager이 ApplicationContext에 정의될 것이다. AbstractTransactionalTestNGSpringContextTests를 확장할 때 다음의 protected 인스턴스 변수에 접근할 수 있다.
    • applicationContext: AbstractTestNGSpringContextTests 수퍼클래스를 상속받는다. 명시적인 빈 검색을 수행하거나 컨텍스트의 상태를 전체적으로 테스트할 때 이 변수를 사용해라.
    • simpleJdbcTemplate: 데이터베이스 쿼리를 하는 SQL 문을 실행하려면 이 변수를 사용해라. 이러한 쿼리는 데이터베이스와 관련된 어플리케이션 코드의 실행 이전과 이후 모두에 데이터베이스 상태를 확인하는 데 사용할 수 있고 스프링이 이러한 쿼리가 어플리케이션 코드와 같은 트랜잭션의 범위로 실행된다는 것을 보장한다. ORM 도구와 함게 사용할 때는 false positives를 피하도록 해야한다.
Tip
이 러한 클래스들은 확장하기 쉽다. 테스트 클래스가 스프링에 특화된 클래스 계층에 묶이길 원하지 않는다면(예를 들어 테스트하는 클래스를 직접 확장하고자 한다면) @ContextConfiguration, @TestExecutionListeners 등을 사용하거나 수동으로 테스트 클래스를 TestContextManager로 구성해서 자신만의 커스텀 테스트 클래스를 설정할 수 있다. 어떻게 테스트 클래스를 구성하는지에 대한 예제는 AbstractTestNGSpringContextTests의 소스코드를 참고해라.


10.3.6 PetClinic 예제
샘 플 저장소에 있는 PetClinic 어플리케이션은 JUnit 4.5+ 환경에서 Spring TestContext Framework의 여러가지 기능을 설명한다. 대부분의 테스트 기능은 AbstractClinicTests에 포함되어 있고 일부 기능이 다음 목록에 나와있다.

Java

import static org.junit.Assert.assertEquals;
// import ...

@ContextConfiguration
public abstract class AbstractClinicTests extends AbstractTransactionalJUnit4SpringContextTests {

  @Autowired
  protected Clinic clinic;

  @Test
  public void getVets() {
    Collection<Vet> vets = this.clinic.getVets();
    assertEquals("JDBC query must show the same number of vets", super.countRowsInTable("VETS"), vets.size());
    Vet v1 = EntityUtils.getById(vets, Vet.class, 2);
    assertEquals("Leary", v1.getLastName());
    assertEquals(1, v1.getNrOfSpecialties());
    assertEquals("radiology", (v1.getSpecialties().get(0)).getName());
    // ...
  }

  // ...
}

Notes:

  • 이 테스트 케이스는 AbstractTransactionalJUnit4SpringContextTests 클래스를 확장한다. 의존성 주입 (DependencyInjectionTestExecutionListener를 통해서)과 트랜잭션 동작(TransactionalTestExecutionListener를 통해서)을 위해서 설정을 상속받는다.
  • clinic 인스턴스 변수(어플리케이션 객체는 테스트되었다.)는 @Autowired를 통해 의존성 주입으로 설정된다.
  • testGetVets() 메서드는 주어진 테이블의 열(row) 갯수를 쉽게 검증해서 어플리케이션 코드의 올바른 동작을 검증하는 상속받은 countRowsInTable() 메서드를 어떻게 사용할 수 있는지 설명한다. 이를 통해 테스트는 더 강력해지고 정확한 테스트 데이터에 대한 의존성은 줄어든다. 예를 들어 테스트를 깨뜨리지 않고 데이터베이스의 추가적인 열을 추가할 수 있다.
  • 데 이터베이스를 사용하는 수많은 통합 테스트처럼 AbstractClinicTests의 테스트 대부분은 테스트 케이스 실행 이전에 이미 데이터베이스에 있는 데이터의 최소 양에 의존한다. 아니면 테스트 클래스의 테스트 픽스처 구성과정에서 데이터베이스의 존재하는 데이터를 선택할 수 있다.(다시 말하지만 테스트와 같은 트랜잭션에 있다.)
PetClinic 어플리케이션은 JDBC, Hibernate, JPA의 세가지 데이터 접근 기술을 지원한다. 리소스 위치를 지정하지 않고 @ContextConfiguration를 선언함으로써 AbstractClinicTests는 기본 위치인 AbstractClinicTests-context.xml에서 로드한 어플리케이션 컨텍스트를 가질 것이다. AbstractClinicTests-context.xml는 공통 DataSource를 선언한다. 서브클래스들은 PlatformTransactionManager와 Clinic의 구현체를 반드시 선언하는 부가적인 컨텍스트 위치를 지정한다.

예 를 들어 PetClinic 테스트의 하이버네이트 구현체는 다음의 구현체를 가진다. 이 예제에서 HibernateClinicTests는 한 줄의 코드도 가지고 있지 않다. @ContextConfiguration만 선언하고 테스트들은 AbstractClinicTests를 상속받는다. @ContextConfiguration를 어떤 리소스 위치도 지정하지 않고 선언했기 때문에 스프링 TestContext 프레임워크는 AbstractClinicTests-context.xml(예시, 상속받은 위치)와 HibernateClinicTests-context.xml에 정의된 모든 빈에서 어플리케이션 컨텍스트를 로드한다. HibernateClinicTests-context.xml는 AbstractClinicTests-context.xml에 정의한 빈을 오버라이드 할 수 있다.

Java

@ContextConfiguration
public class HibernateClinicTests extends AbstractClinicTests { }

대 규모 어플리케이션에서 스프링 설정은 종종 여러 파일로 나뉜다. 따라서 설정 위치는 모든 어플리케이션에 한정된 통합 테스트의 공통 기반 클래스에 보통 지정한다. 이러한 기반 클래스는 하이버네이트를 사용하는 어플리케이션의 경우 SessionFactory같은 유용한 인스턴스 변수(자연히 의존성 주입으로 존재한다.)를 추가할 수도 있다.

가능한한 통합테스트에서 배포된 환경과 정확히 같은 스프링 설정을 가져야 한다. 다를 수도 있는 부분은 데이터베이스 연결 풀링과 트랜잭션 인프라에 관련된 것들이다. 완전한 어플리케이션 서버에 배포한다면 아마도 어플리케이션 서버의 연결풀(JNDI로 사용가능한)과 JTA 구현체를 사용할 것이다. 그러므로 프로덕션에서는 DataSource와 JtaTransactionManager에 JndiObjectFactoryBean나 <jee:jndi-lookup>를 사용할 것이다. JNDI와 JTA는 통합테스트 컨테이너 밖에서는 사용할 수 없으므로 이를 위해서는 Commons DBCP BasicDataSource와 DataSourceTransactionManager 또는 HibernateTransactionManager 같은 조합을 사용해야 한다. 테스트와 프로덕션 환경사이에 다르지 않은 설정들과 분리한 어플리케이션 서버설정과 '로컬' 설정사이에 선택할 수 있는 하나의 XML 파일에 이 다양한 동작을 넣을 수 있다. 추가적으로 연결 설정에 프로퍼티 파일을 사용하는 것은 바람직하다. 예제는 PetClinic 어플리케이션을 참고해라.

10.4 추가 자료
테스트에 관한 더 자세한 정보는 다음의 자료들을 참고해라.

  • JUnit: “프로그래머 지향 자바 테스트 프레임워크”. 스프링 프레임워크가 테스트 슈트에 사용하고 있다.
  • TestNG: JUnit에서 영감을 받아 자바 5 어노테이션, 테스트 그룹, 데이터 주도 테스트, 분산 테스트등을 추가한 테스트 프레임워크
  • MockObjects.com: 테스트 주도 개발에서 코드의 설계를 향상시키는 기술인 목 객체를 위한 웹 사이트.
  • "목 객체": 위키피디아의 글.
  • EasyMock: “자바의 프록시 메카니즘을 사용해서 인터페이스에 대한 목 객체를 생성해서 제공하는 ”자바 라이브러리. 스프링 프레임워크가 테스트 슈트에 사용하고 있다.
  • JMock: 목 객체로 자바 코드의 테스트 주도 개발을 지원하는 라이브러리
  • Mockito: 테스트 스파이 패턴에 기반한 자바 목 라이브러리
  • DbUnit: 테스트 실행간에 알고 있는 상태를 데이터베이스에 두는(다른 것들도 있지만) 데이터베이스 주도 프로젝트를 대상으로 하는 JUnit 익스텐션(Ant와 메이븐과도 사용할 수 있다.)
  • Grinder: 자바 부하 테스트 프레임워크.

[JAVA] 10장 테스트 #1..

출처 : Outsider's Dev Story https://blog.outsider.ne.kr/

이 문서는 개인적인 목적이나 배포하기 위해서 복사할 수 있다. 출력물이든 디지털 문서든 각 복사본에 어떤 비용도 청구할 수 없고 모든 복사본에는 이 카피라이트 문구가 있어야 한다.

10. 테스트
10.1 스프링 테스트 소개
테스팅은 엔터프라이즈 소프트웨어 개발에서 필수적이다. 이번 장에서는 유닛 테스트에 대한 IoC 원리의 부가가치와 통합 테스트의 이점에 집중한다. (엔터프라이즈에서 테스트를 전부 다루는 것은 이 레퍼런스 매뉴얼의 번위를 넘어선다.)

10.2 유닛 테스트
의존성 주입은 전통적인 자바 EE 개발이 하던 것보다 코드가 컨테이너에 덜 의존적이도록 한다. 어플리케이션을 구성하는 POJO는 스프링이나 다른 컨테이너없이 new 오퍼레이터를 사용해서 객체를 간단히 인스턴스화해서 JUnit과 TestNG로 테스트 할 수 있어야 한다. 코드를 독립적으로 테스트할 때 목(mock) 객체를(다른 가치있는 테스트 기법과 함께) 사용할 수 있다. 스프링의 아키텍쳐 권장사항을 따른다면 코드의 계층화(layering)와 컴포넌트화가 깔끔하게 될 것이고 훨씬 쉽게 유닛 테스트를 할 수 있다. 예를 들어 유닛 테스트를 하는 동안 퍼시스턴트 데이터에 접근하지 않고도 DAO나 레파지토리 인터페이스를 스텁(stubbing)하거나 모킹(mocking)해서 서비스 계층의 객체들을 테스트 할 수 있다.

실제 유닛테스트는 설정해야 할 런타임 인프라스트럭쳐가 없기 때문에 정말 빠르게 수행된다. 개발 방법론에서 유닛 테스트는 생성성은 아주 높혀줄 것이다. IoC 기반 어플리케이션에서 효율적인 유닛 테스트를 작성하는 데는 테스트 장의 이번 섹션은 별로 도움이 되지 않을 것이다. 하지만 유닛 테스트 시나리오들을 위해서 스프링 프레임워크는 다음의 목 객체와 테스트 지원 클래스를 제공한다.

10.2.1 목 객체
10.2.1.1 JNDI
org.springframework.mock.jndi 패키지는 테스트 슈트나 독립적인 어플리케이션의 간단한 JNDI 환경을 설정하는데 사용할 수 있는 JNDI SPI 구현체를 포함하고 있다. 예를 들어 JDBC DataSource가 자바 EE 컨테이너내에서 처럼 테스트 코드에서 간은 JNDI 이름들에 바인딩된다면 수정없이도 테스트 시나리오에서 어플리케이션 코드와 설정을 모두 재사용할 수 있다.

10.2.1.2 서블릿 API
org.springframework.mock.web 패키지는 웹 컨텍스트와 컨트롤러를 테스트하는데 유용한 스프링의 웹 MVC 프레임워크와 함께 사용할 수 있는 서블릿 API 목 객체의 광범위한 세트를 포함하고 있다. 이러한 목 객체들은 EasyMock같은 동적 목 객체들이나 MockObjects같은 이미 존재하는 서블릿 API 목 객체들보다 일반적으로 사용하기가 더 편하다.

10.2.1.3 포틀릿(Portlet) API
org.springframework.mock.web.portlet 패키지는 스프링의 포틀릿 MVC 프레임워크와 사용하도록 만들어진 포틀릿 API 목 객체들의 세트를 포함하고 있다.

10.2.2 유닛테스트를 지원하는 클래스
10.2.2.1 일반적인 유틸리티
org.springframework.test.util 패키지는 리플렉션기반의 유틸리티 메서드의 컬렉션인 ReflectionTestUtils를 포함하고 있다. 개발자들은 어플리케이션 코드를 테스트할 때 public이 아닌 필드를 설정하거나 public이 아닌 setter 메서드를 호출해야 하는 유닛 테스트나 통합 테스트 시나리오에서 이 메서드들을 사용한다. 다음과 같은 예시들이 있다.

  • 도메인 엔티티에서 프로퍼티에 대한 public setter 메서드와는 반대로 private 필드나 protected 필드에 접근하는 JPA나 하이버네이트같은 ORM 프레임워크.
  • private필드, protected 필드, setter 메서드, 설정 메서드에 의존성을 주입하는 @Autowired, @Inject, @Resource,같은 어노테이션의 스프링 지원

10.2.2.2 스프링 MVC
org.springframework.test.web 패키지는 ModelAndViewAssert를 포함하고 있다. ModelAndViewAssert는 Spring MVC ModelAndView 객체들을 다루는 유닛테스트에 JUnit과 TestNG나 다른 테스트 프레임워크와 함께 사용할 수 있다.

스프링 MVC 컨트롤러의 유닛 테스트
Spring MVC Controller를 테스트하려면 org.springframework.mock.web 패키지의 MockHttpServletRequest, MockHttpSession 등과 결합해서 ModelAndViewAssert를 사용해라.


10.3 통합 테스트
10.3.1 개요
어플리케이션 서버에 배포를 하거나 다른 엔터프라이즈 인프라에 연결하지 않고도 할 수 있는 통합테스트는 중요하다. 이러한 통합테스트는 다음을 테스트 할 수 있게 한다.

  • 스프링 IoC 컨테이너 컨텍스트의 제대로 된 연결
  • JBDC나 ORM 도구를 사용한 데이터 접근. 이는 SQL문, 하이버네이트 쿼리, JPA 엔티티 맵핑 등의 정확성 테스트를 포함한다.
스프링 프레임워크는 spring-test로 통합테스트를 훌륭하게 지원한다. 실제 JAR 파일의 이름에는 릴리즈 버전을 포함될 것이고 어디서 다운받는가에 따라(자세한 내용은 의존성 관리 부분을 참고해라.) 긴 형식의 org.springframework.test도 포함될 것이다. 이 라이브러리는 스프링 컨테이너의 통합테스트에 대한 중요한 클래스들은 담고 있는 org.springframework.test 패키지를 포함하고 있다. 이 테스트는 어플리케이션 서버나 다른 배포 환경에 의존하지 않는다. 이러한 테스트는 유닛 테스트보다는 느리지만 동등한 Cactus 테스트나 어플리케이션 서버에 배포해서 테스트 하는 원격 테스트보다는 훨씬 빠르다.

스프링 2.5부터는 유닛테스트와 통합테스트를 어노테이션 주도 Spring TestContext Framework의 형식으로 지원한다. TestContext 프레임워크는 사용하는 실제 테스트 프레임워크와는 관계없이 별도로 동작하므로 JUnit, TestNG 등을 포함한 다양한 환경에서 테스트수단을 제공한다.

JUnit 3.8 지원은 폐기됨
스프링 3.0부터 레거시 JUnit 3.8 기반의 클래스 계층(예시 AbstractDependencyInjectionSpringContextTests, AbstractTransactionalDataSourceSpringContextTests 등등)은 공식적으로 폐기되었고 차후 릴리즈버전에서는 제거될 것이다. 이 클래스들에 기반한 테스트 클래스들은 모두 Spring TestContext Framework로 마이그레이션해야 한다.

스프링 3.1부터는 Spring TestContext Framework의 JUnit 3.8 기반 클래스들 (예시 AbstractJUnit38SpringContextTests, AbstractTransactionalJUnit38SpringContextTests)과 @ExpectedException는 공식적으로 폐기되었고 차후 버전에서는 제거될 것이다. 여기에 기반하고 있는 테스트 클래스들은 모두 Spring TestContext Framework가 제공하는 JUnit 4나 TestNG 지원으로 마이그레이션해야 한다. 유사하게 @ExpectedException 어노테이션이 붙은 테스트 메서드는 JUnit과 TestNG에서 기대하는 예외에 대한 내장된 지원을 사용하도록 수정해야 한다.


10.3.2 통합테스트의 목표
스프링의 통합테스트 지원에는 다음의 주요 목표들이 있다.

  • 테스트 실행간에 Spring IoC 컨테이너 캐싱을 관리하기 위해서
  • 테스트 픽스처 인스턴스의 의존성 주입을 제공하기 위해서
  • 통합 테스트의 적절한 트랜잭션 관리를 제공하기 위해서
  • 통합테스트를 작성하는 개발자들을 지원하는 스프링기반 클래스를 제공하려고
다음의 섹션들은 각 목표들을 설명하고 자세한 구현체와 설정에 대한 링크를 제공한다.

10.3.2.1 컨텍스트 관리와 캐싱
스프링 TestContext 프레임워크는 일관되게 스프링 ApplicationContext를 로딩하고 이러한 컨텍스트들을 캐싱한다. 구동시간이 이슈가 될 수 있으므로 로딩된 컨텍스트 캐싱기능은 중요하다. (스프링 자체의 부하가 아니라 스프링 컨테이너가 인스턴스화된 객체들을 인스턴스화하는데 걸리는 시간 때문이다.) 예를 들어 50 ~ 100개의 하이버네이트 매핑파일을 가진 프로젝트는 매핑파일들을 로딩하는데 10 ~ 20초가 걸릴 것이고 각 테스트 픽스처의 각 테스트를 실행하기 전에 비용을 초래해서 전체 테스트 실행을 더 느리게 해서 생산성이 줄어들 수 있다.

테스트 클래스들은 XML 설정 메타데이터의 리소스 위치(보통은 클래스패스)를 담고 있는 배열이나 어플리케이션을 설정하는데 사용하는 @Configuration 클래스를 담고 있는 배열을 제공할 수 있다. 이러한 경로나 클래스는 web.xml이나 다른 배포 설정파일에서 지정한 것과 같거나 비슷하다.

기본적으로 설정된 ApplicationContext가 일단 로드되면 각 테스트마다 재사용된다. 그러므로 설정비용은 (테스트슈트당) 딱 한번만 발생하고 이어진 테스트 실행은 훨씬 빨라진아. 여기서 테스트 슈트라는 말은 같은 JVM에서 실행되는 모든 테스트를 의미한다. (예를 들어 해당 프로젝트나 모듈을 빌드하는 Ant나 Maven으로 실행하는 모든 테스트) 빈도수는 적지만 어플리케이션 컨텍스트를 깨뜨리는 테스트나 재로딩이 필요한 경우(예를 들어 빈 정의나 어플리케이션 객체의 상태를 수정해서) 다음 테스트를 실행하기 전에 설정을 다시 로딩하고 어플리케이션을 다시 빌드하도록 TestContext 프레임워크를 설정할 수 있다.

TestContext 프레임워크의 컨텍스트 관리와 캐싱을 봐라.

10.3.2.2 테스트 픽스처의 의존성 주입
TestContext 프레임워크가 어플리케이션 컨텍스트를 로드했을 때 TestContext 프레임워크는 의존성 주입으로 테스트 클래스의 인스턴스를 선택적으로 설정할 수 있다. 이는 어플리케이션 컨텍스트에서 미리 설정된 빈을 사용하는 테스트 픽스처를 설정하는 편리한 메카니즘을 제공한다. 다양한 테스트 시나리오(예시. 스프링이 관리하는 객체 그래프, 트랜잭션 프록시, DataSource 등을 설정하기 위해서)에 걸쳐서 어플리케이션 컨텍스트를 재사용할 수 있다는 큰 장점이 있으므로 각 테스트케이스에 복잡한 테스트 픽스처를 중복해서 설정하지 말아라.

예제와 같이 Title 도메인 엔티티에 대한 데이터 접근 로직을 수행하는 HibernateTitleRepository 클래스를 가진 시나리오를 생각해 보자. 다음 영역을 테스트하는 통합 테스트를 작성한다고 해보자.

  • 스프링 설정: 기본적으로 HibernateTitleRepository 빈의 설정과 관련된 모든 것이 제대로 되었고 존재하는가?
  • 하이버네이트 매핑 파일 설정: 모두 제대로 매핑되었고 지연로딩 설정이 제위치에 있는가?
  • HibernateTitleRepository의 로직: 이 클래스의 설정된 인스턴스가 예상대로 동작하는가?
TestContext 프레임워크의 테스트 픽스처의 의존성 주입을 봐라.

10.3.2.3 트랜잭션 관리
실제 데이터베이스에 접근하는 테스트의 공통적인 이슈는 퍼시스턴스 스토어의 상태에 영향을 받는다는 것이다. 개발용 데이터베이스를 사용하더라도 상태가 바뀌면 테스트에 영향을 줄 것이다. 또한 퍼시스턴트 데이터를 추가하거나 수정하는 것 같은 많은 작업은 트랜잭션 밖에서 수행(또는 확인)할 수 없다.

TestContext 프레임워크는 이 문제를 해결한다. 기본적으로 TestContext 프레임워크는 각 테스트마다 트랜잭션을 만들고 롤백한다. 개발자들은 트랜잭션의 존재를 가정하고 그냥 코드를 작성한다. 테스트에서 프록시된 객체들을 트랜잭션으로 호출하면 설정된 트랜잭션 시맨틱에 따라 제대로 동작할 것이다. 추가적으로 테스트 메서드가 트랜잭션내에서 선택한 테이블의 내용을 지우면 트랜잭션은 기본적으로 롤백을 하고 데이터베이스는 테스트를 수행하기에 앞서 데이터베이스의 상태를 원래대로 돌릴 것이다. 트랜잭션 지원이 테스트의 어플리케이션 컨텍스트에서 정의된 PlatformTransactionManager 빈으로 테스트 클래스에 제공된다.

트랜잭션을 커밋하고 싶다면(일반적이지는 않지만 특정 테스트가 유지되거나 데이터베이스를 수정하기를 원하는 경우에 유용하다.) @TransactionConfiguration와 @Rollback 어노테이션으로 트랜잭션을 롤백하는 대신에 커밋하도록 TestContext 프레임워크에 지시할 수 있다.

TestContext 프레임워크의 트랜잭션 관리부분을 봐라.

10.3.2.4 통합테스트를 지원하는 클래스
스프링 TestContext 프레임워크는 통합테스트 작성을 쉽게 해주는 여러가지 abstract 지원 클래스들을 제공한다. 이 기반 테스트 클래스(base test classes)는 개발자가 접근할 수 있는 편리한 인스턴스 변수와 메서드뿐만 아니라 잘 정의된 테스트 프레임워크 훅(hook)을 제공한다.

명시적인 빈 검색을 수행하거나 컨텍스트의 상태를 전체적으로 테스트하는 ApplicationContext
디비를 조회 SQL문을 실행하는 SimpleJdbcTemplate. 이러한 쿼리를 데이터베이스와 관련된 어플리케이션 코드를 실행하기 이전과 이후에 데이터베이스의 상태를 확인하는데 사용할 수 있고 스프링이 어플리케이션과 같은 트랜잭션 범위에서 이러한 쿼리를 실행한다는 것을 보장해준다. ORM 도구를 함께 사용하는 경우에는 false positives를 피해야 한다.

추가적으로 프로젝트에 한정된 인스턴스 변수와 메서드를 가진 어플리케이션 범위의 커스텀 슈퍼클래스를 생성하고자 할 수 있다.

TestContext 프레임워크의 지원 클래스를 참고해라.

10.3.3 JDBC 테스트 지원
org.springframework.test.jdbc 패키지는 표준적인 데이터베이스 테스트 시나리오를 간결하게 하는 JDBC 관련 유티리티 함수의 컬렉션인 SimpleJdbcTestUtils를 포함하고 있다. AbstractTransactionalJUnit4SpringContextTests와 AbstractTransactionalTestNGSpringContextTests는 내부적으로 SimpleJdbcTestUtils에 위임하는 편리한 메서드를 제공한다.

10.3.4 어노테이션
10.3.4.1 스프링 테스트 어노테이션
스프링 프레임워크는 유닛테스트와 통합테스트에서 TestContext 프레임워크와 결합해서 사용할 수 있는 스프링에 특화된 어노테이션의 다음 셋을 제공한다. 기본적인 속성값, 속성 별칭 등 더 자세한 정보는 각각의 Javadoc를 참고해라.

  • @ContextConfiguration
    테스트 클래스에 대해 ApplicationContext를 어떻게 로딩하고 어떻게 설정하는지 결정하는 클래스수준의 메타에디터를 정의해라. 특히 @ContextConfiguration는 컨텍스트를 로딩하는데 사용하는 ContextLoader 전략뿐만 아니라 어플리케이션 컨텍스트 리소스 locations나 @Configuration classes (하지만 둘 다는 아니다.)를 선언한다. 하지만 기본 로더가 리소스 locations나 설정 classes를 지원하기 때문에 보통은 명시적으로 로더를 설정할 필요가 없다.
    Java
    
    @ContextConfiguration(locations="example/test-context.xml", loader=CustomContextLoader.class)
    public class XmlApplicationContextTests {
      // class body...
    }
    

    Java
    
    @ContextConfiguration(classes=MyConfig.class)
    public class ConfigClassApplicationContextTests {
      // class body...
    }
    

    Note
    @ContextConfiguration는 기본적으로 수퍼클래스가 선언한 리소스 경로나 설정 클래스를 상속하도록 지원한다.

    더 자세한 내용과 예제는 컨텍스트 관리와 캐싱와 Javadoc을 참고해라.
  • @ActiveProfiles
    테스트 클래스에 대해서 ApplicationContext를 로딩할 때 어떤 빈 정의 프로파일이 활성화되어야 하는 지를 선언하려고 사용하는 클래스 수준의 어노테이션이다.
    Java
    
    @ContextConfiguration
    @ActiveProfiles("dev")
    public class DeveloperTests {
      // class body...
    }
    

    Java
    
    @ContextConfiguration
    @ActiveProfiles({"dev", "integration"})
    public class DeveloperIntegrationTests {
      // class body...
    }
    

    Note
    @ActiveProfiles는 기본적으로 수퍼클래스가 선언한 활성화된 빈 선언 프로파일을 상속하도록 지원한다.

    @ActiveProfiles의 더 자세한 내용과 예제는 환경 프로파일을 사용한 컨텍스트 설정와 Javadoc을 참고해라.
  • @DirtiesContext
    테스트 실행 중에 의존하는 스프링 ApplicationContext가 오염(dirtied)(예시. 어떤 방법으로 수정되거나 오류가 생김)되었고 테스트 통과여부와 상관없이 반드시 닫아야 한다는 것을 나타낸다. @DirtiesContext는 다음 시나리오에서 지원한다.
    • 현재 테스트 클래스 이후 클래스모드가 기본 클래스모드인 AFTER_CLASS로 설정된 클래스에 선언한 경우.
    • 현재 테스트 클래스에서 각 테스트 메서드 이후 클래스 모드가  AFTER_EACH_TEST_METHOD.로 설정된 클래스에 선언한 경우.
    • 현재 테스트 이후 메서드에 선언한 경우.
    테스트가 컨텍스트를 수정한 경우(예를 들어 빈 정의를 교체한 경우) 이 어노테이션을 사용해라. 이어지는 테스트들은 새로운 컨텍스트를 사용한다.

    JUnit 4.5+나 TestNG를 사용할 때는 같은 테스트 클래스내에서 클래스 수준 어노테이션과 메서드 수준 어노테이션으로 모두 @DirtiesContext 를 사용할 수 있다. 이러한 시나리오에서 전체 클래스 이후 뿐만 아니라 이러한 어노테이션이 붙은 메서드 이후에 ApplicationContext 를 dirty로 표시한다. ClassMode를 AFTER_EACH_TEST_METHOD로 설정했으면 클래스의 각 테스트 메서드 이후에 컨텍스트를 dirty로 표시한다.
    Java
    
    @DirtiesContext
    public class ContextDirtyingTests {
      // 스프링 컨테이너가 오염되는 테스트들
    }
    

    Java
    
    @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
    public class ContextDirtyingTests {
      // 스프링 컨테이너가 오염되는 테스트들
    }
    

    Java
    
    @DirtiesContext
    @Test
    public void testProcessWhichDirtiesAppCtx() {
      // 스프링 컨테이너가 오염되는 테스트들
    }
    

    어플리케이션 컨텍스트가 dirty로 표시되면 어플리케이션 컨텍스트는 테스트 프레임워크의 캐시에서 제거되고 종료된다. 그래서 같은 리소스 위치의 세트를 가진 컨텍스트를 필요로 하는 이어진 테스트를 위해서 의존하는 스프링 컨테이너를 재구성한다.
  • @TestExecutionListeners
    TestContextManager에 어떤 TestExecutionListener들이 등록되어야 하는지 설정하는 클래스수준의 메타데이터를 정의한다. 보통 @TestExecutionListeners는 @ContextConfiguration와 결합해서 사용한다.
    Java
    
    @ContextConfiguration
    @TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
    public class CustomTestExecutionListenerTests {
      // class body...
    }
    

    @TestExecutionListeners는 기본적으로 상속받은 리스너를 지원한다. 자세한 내용과 예제는 Javadoc를 참고해라.
  • @TransactionConfiguration
    트랜잭션이 적용된 테스트를 설정하는 클래스수준의 메타데이터를 정의한다. 특히, 원하는 PlatformTransactionManager의 빈 이름이 "transactionManager"가 아니라면 트랜잭션을 유도하는데 사용하는 PlatformTransactionManager의 빈 이름을 명시적으로 설정할 수 있다. 추가적으로 defaultRollback 플래그를 false로 변경할 수 있다. 보통 @TransactionConfiguration는 @ContextConfiguration와 결합해서 사용한다.
    Java
    
    @ContextConfiguration
    @TransactionConfiguration(transactionManager="txMgr", defaultRollback=false)
    public class CustomConfiguredTransactionalTests {
      // class body...
    }
    

    Note
    기본적인 관례로도 테스트 설정이 충분하다면 @TransactionConfiguration을 전혀 사용하지 않을 수 있다. 즉, 트랜잭션 매니저 빈의 이름이 "transactionManager"이고 트랜잭션을 자동으로 롤백하고자 한다면 테스트 클래스에 @TransactionConfiguration 어노테이션을 붙힐 필요가 없다.

  • @Rollback
    어노테이션이 붙은 메서드가 완료된 후에 트랜잭션을 롤백해야 하는지를 나타낸다. true인 경우 트랜잭션을 롤백하고 false이면 트랜잭션을 커밋한다. 클래스 수준에서 설정한 기본 롤백 플래그를 오버라이드하기 위해서 @Rollback를 사용해라.
    Java
    
    @Rollback(false)
    @Test
    public void testProcessWithoutRollback() {
      // ...
    }
    
  • @BeforeTransaction
    @Transactional 어노테이션을 통한 트랜잭션내에서 실행하려고 설정한 테스트 메서드에 트랜잭션을 시작하기 이전에 어노테이션이 붙은 public void 메서드를 실행해야 하는지를 나타낸다.
    Java
    
    @BeforeTransaction
    public void beforeTransaction() {
      // 트랜잭션이 시작되기 전에 실행해야 하는 로직
    }
    
  • @AfterTransaction
    @Transactional 어노테이션을 통한 트랜잭션내에서 실행하려고 설정한 테스트 메서드에 트랜잭션이 종료된 이후에 어노테이션이 붙은 public void 메서드를 실행해야 하는지를 나타낸다.
    Java
    
    @AfterTransaction
    public void afterTransaction() {
      // 트랜잭션이 종료된 이후에 실행해야 하는 로직
    }
    
  • @NotTransactional
    이 어노테이션이 붙은 테스트 메서드는 트랜젝션이 적용된 컨텍스트에서 실행하지 말아야 한다는 것을 나타낸다.
    Java
    
    @NotTransactional
    @Test
    public void testProcessWithoutTransaction() {
      // ...
    }
    
@NotTransactional는 폐기되었다
트랜잭션을 적용하지 않는 테스트 메서드는 별도의 (트랜잭션이 적용되지 않은) 테스트 클래스나 @BeforeTransaction, @AfterTransaction 메서드로 옮겨야 한다는 점에 지지해서 스프링 3.0부터는 @NotTransactional가 폐기되었다. 전체 클래스에 @Transactional 어노테이션을 붙히는 대신에 개별 메서드에 @Transactional 어노테이션을 붙히는 것을 고려해 봐라. 이렇게 하면 @NotTransactional을 사용할 필요가 없이 트랜잭션 메서드와 트랜잭션이 아닌 메서드를 같은 테스트 클래스내에서 섞어서 사용할 수 있다.


10.3.4.2 표준 어노테이션 지원
다음 어노테이션들은 스프링 TestContext 프레임워크의 모든 설정에서 표준적인 의미로 지원된다. 이 어노테이션들은 테스트에 한정되지 않고 스프링 프레임워크내 어디서나 사용할 수 있다.

  • @Autowired
  • @Qualifier
  • @Resource (javax.annotation) JSR-250을 제공했다면
  • @Inject (javax.inject) JSR-330을 제공했다면
  • @Named (javax.inject) JSR-330을 제공했다면
  • @PersistenceContext (javax.persistence) JPA를 제공했다면
  • @PersistenceUnit (javax.persistence) JPA를 제공했다면
  • @Required
  • @Transactional

10.3.4.3 스프링 JUnit 테스트 어노테이션
다음의 어노테이션들은 SpringJUnit4ClassRunner나 JUnit 지원클래스와 함께 사용할 때만 사용할 수 있다.

  • @IfProfileValue
    어노테이션이 붙은 테스트가 특정 테스트환경에서 사용가능하게 된다는 것을 나타낸다. ProfileValueSource가 제공된 name과 일치하는 value를 반환하면 테스트는 활성화된다. 이 어노테이션은 전체 클래스나 개별 메서드에 적용할 수 있다. 클래스수준의 적용하면 메서드 수준의 적용한 설정을 덮어쓴다.
    Java
    
    @IfProfileValue(name="java.vendor", value="Sun Microsystems Inc.")
    @Test
    public void testProcessWhichRunsOnlyOnSunJvm() {
      // Sun 마이크로시스템즈의 자바 VM에서만 동작하는 로직
    }
    

    대신에 JUnit 환경에서 TestNG형태의 테스트 그룹을 지원하기 위해서 values 목록(OR의 의미로)의 @IfProfileValue를 설정할 수 있다. 다음의 예제를 봐라.
    Java
    
    @IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
    @Test
    public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
      // 유닛테스트와 통합테스트 그룹에서만 동작하는 로직
    }
    
  • @ProfileValueSourceConfiguration
    @IfProfileValue 어노테이션으로 설정된 프로파일 값을 획득할 때 어떤 타입의 ProfileValueSource를 사용할 지를 지정하는 클래스수준의 어노테이션이다. @ProfileValueSourceConfiguration을 테스트에 선언하지 않았다면 기본값인 SystemProfileValueSource를 사용한다.
    Java
    
    @ProfileValueSourceConfiguration(CustomProfileValueSource.class)
    public class CustomProfileValueSourceTests {
      // class body...
    }
    
  • @Timed
    어노테이션이 붙은 테스트 메서드가 반드시 지정된 시간내에(밀리초단위) 실행이 완료되어야 한다는 것을 나타낸다. 테스트 실행시간이 지정된 시간을 넘으면 테스트는 실패한다.

    여기서 시간은 테스트 픽스처의 set up이나 tear down, 테스트의 반복(@Repeat 참고), 테스트 메서드 자체의 실행을 포함한다.
    Java
    
    @Timed(millis=1000)
    public void testProcessWithOneSecondTimeout() {
      // 실행에 1초이상이 걸리지 않는 로직
    }
    

    스프링의 @Timed 어노테이션은 JUnit의 @Test(timeout=...)와는 의미가 다르다. 특히 JUnit이 테스트 실행 타임아웃을 다루는 방법때문에 @Test(timeout=...)는 반복(repetitions)의 경우 각 반복(iteration)에 적용해서 테스트가 너무 오래 걸릴때 테스트가 실패하는 것은 막아준다. 반면 스프링의 @Timed의 시간은 테스트를 실행하는 전체 시간이고(모든 반복을 포함해서) 테스트가 실패하는 것을 막아주지 않고 실패전에 테스트가 종료하기를 기대한다.
  • @Repeat
    이 어노테이션이 붙은 메서드는 반드시 반복적으로 실행되어야 한다는 것을 나타낸다. 테스트 메서드가 실행되는 반복수는 어노테이션에 지정한다.

    반복하는 실행의 범위는 테스트 메서드 자체의 실행뿐만 아니라 테스트 픽스처의 set up이나 tear down도 포함한다.
    Java
    
    @Repeat(10)
    @Test
    public void testProcessRepeatedly() {
      // ...
    }
    

10.3.5 스프링 TestContext 프레임워크
스프링 TestContext 프레임워크(org.springframework.test.context 패키지에 있다.) JUnit이나 TestNG같은 사용하는 테스트 프레임워크에 상관없이 일반적이면서 어노테이션 주도의 유닛테스트와 통합테스트를 지원한다. TestContext 프레임워크는 어노테이션 기반 설정이 오버라이드할 수 있는 합당한 기본값으로 설정보다 관례(convention over configuration)의 중요성을 크게 적용했다.

일반적인 테스트 인프라스트럭처에 추가적으로 TestContext 프레임워크는 abstract 지원 클래스의 형태로 JUnit과 TextNG를 명시적으로 지원한다. JUnit의 경우에는 스프링이 POJO 테스트 클래스를 작성할 수 있도록 하는 JUnit Runner도 제공한다. POJO 테스트 클래스는 특정 클래스 계층을 확장(extend)해야할 필요가 없다.

다음 섹션에서는 TestContext 프레임워크의 내부를 살펴본다. TestContext 프레임워크의 사용방법에만 관심있고 커스텀 리스너나 커스텀 로더로 TestContext 프레임워크를 확장하는 방법에는 관심이 없다면 설정(컨텍스트 관리, 의존성 주입, 트랜잭션 관리) 지원 클래스와 어노테이션 지원 섹션으로 건너띄워도 무방하다.

10.3.5.1 핵심 추상화
TestContext 프레임워크의 코어는 TestContext, TestContextManager 클래스와 TestExecutionListener, ContextLoader, SmartContextLoader 인터페이스로 이루어져 있다. TestContextManager는 테스트마다 생성된다.(예시. JUnit에서 하나의 테스트 메서드의 실행마다) TestContextManager는 결국 현재 테스트의 컨텍스트를 담고 있는 TestContext를 관리한다. TestContextManager도 테스트가 의존성 주입 제공, 트랜잭션 관리 등으로 실제 테스트 실행하는 TestExecutionListener로 진행시키거나 위임하자마자 TestContext의 상태를 갱신한다. ContextLoader(또는 SmartContextLoader)는 해당 테스트를 위한 ApplicationContext를 로딩하는 책임을 진다. 다양한 구현체의 예제나 더 자세한 내용은 Javadoc과 스프링 테스트 슈트를 참고해라.

  • TestContext: 사용하는 실제 테스트 프레임워크와 무관하게 테스트가 실행되는 컨텍스트를 은닉화하고 컨텍스트 관리와 테스트 인스턴스에 대한 믿을만한 컨텍스트 관리와 캐싱지원을 제공한다. TestContext도 필요한 경우 ApplicationContext를 로드하는 ContextLoader(또는 SmartContextLoader)에 위임한다.
  • TestContextManager: 스프링 TestContext 프레임워크의 주요 진입점이다. 단일 TestContext와 잘 정의된 테스트 실행시 등록된 모든 TestExecutionListener의 신호(signal) 이벤트를 관리한다.
    • 특정 테스트 프레임워크의 before class methods 앞에
    • 테스트 인스턴스 준비
    • 특정 테스트 프레임워크 before methods 앞에
    • 특정 테스트 프레임워크 after methods 이후에
    • 특정 테스트 프레임워크 after class methods 이후에
  • TestExecutionListener: 리스너가 등록된 TestContextManager가 배포한 이벤트의 테스트 실행에 반응하는 listener API를 정의한다.

    스프링은 기본적으로 설정된 세가지 TestExecutionListener 구현체를 제공한다: DependencyInjectionTestExecutionListener, DirtiesContextTestExecutionListener, TransactionalTestExecutionListener. 각각 테스트 인스턴스의 의존성 주입, @DirtiesContext 어노테이션의 제어, 롤백이 기본인 트랜잭션을 적용한 테스트 실행을 지원한다.
  • ContextLoader: 스프링 TestContext 프레임워크가 관리하는 통합 테스트의 ApplicationContext를 로딩하는 Strategy 인터페이스는 스프링 2.5에서 도입되었다.

    스프링 3.1부터는 설정 클래스와 활성화된 빈 정의 프로파일을 지원하기 위해 이 인터페이스 대신 SmartContextLoader를 구현해라.
  • SmartContextLoader: 스프링 3.1에서 도입된 ContextLoader 인터페이스의 확장

    SmartContextLoader SPI는 스프링 2.5에서 도입된 ContextLoader SPI를 대체한다. 특히, SmartContextLoader는 리소스 locations나 설정 classes를 처리할 지를 선택할 수 있다. 게다가 SmartContextLoader는 로드한 컨텍스트에서 활성화된 빈 정의 프로파일을 설정할 수 있다.

    스프링은 다음의 독창적인(out-of-the-box) 구현체들을 제공한다.
    • DelegatingSmartContextLoader: 테스트 클래스에 대해 선언한 설정이나 기본 위치, 기본 설정 클래스의 존재에 따라 내부적으로 AnnotationConfigContextLoader나 GenericXmlContextLoader에 위임하는 기본 로더
    • AnnotationConfigContextLoader: @Configuration 클래스에서 어플리케이션 컨텍스트를 로딩한다.
    • GenericXmlContextLoader: XML 리소스 위치에서 어플리케이션 컨텍스트를 로딩한다.
    • GenericPropertiesContextLoader: 자바 프로퍼티 파일에서 어플리케이션 컨텍스트를 로딩한다.
다음 섹션에서는 어노테이션으로 TestContext 프레임워크를 어떻게 설정하는 지 설명하고 TestContext 프레임워크로 유닛테스트와 통합 테스트를 설정하는 방법의 예제를 볼 것이다.