Spring中文文档翻译Integration Testing(2)

xiaoxiao2021-02-28  29

15.5 Spring TestContext Framework

Spring TestContext Framework(包路径为 org.springframework.test.context)提供了通用的、注解驱动的单元和集成测试支持,这些支持与使用的测试框架无关。TestContext框架还非常重视约定优于配置的约定,可以通过基于注释的配置重写。

除了通用的测试基础结构之外,TestContext框架还以抽象支持类的形式提供了对JUnit 4和TestNG的明确支持。对于JUnit 4,Spring还提供了一个自定义的JUnit运行器和自定义JUnit规则,允许编写所谓的POJO测试类。不需要POJO测试类来扩展特定的类层次结构。

下一节将概述TestContext框架的内部结构。如果您只对使用框架感兴趣,而不希望使用您自己的自定义侦听器或自定义加载器来扩展它,那么您可以直接访问配置(上下文管理、依赖注入、事务管理)、支持类和注释支持部分。

15.5.1 关键概念

框架的核心包括TestContextManager类,TestContext接口,TestExecutionListener接口和SmartContextLoader接口。每个测试类将会创建一个TestContextManager (JUnit 4中一个测试类下的所有测试)。 TestContextManager 管理一个保存目前测试上下文的TestContext 。TestContextManager在测试过程中更新了TestContext的状态,并将其委托给TestExecutionListener操作,后者通过提供依赖注入、管理事务等方式来支持执行测试。SmartContextLoader负责为给定的测试类加载一个ApplicationContext。请参阅javadocs和Spring测试套件,以获得更多信息和各种实现的示例。

TestContext

TestContext封装了执行测试的上下文,隔离了实际测试框架的使用情况,并为对应的测试实例提供了上下文管理和缓存支持。TestContext 在需要的情况下,也提供了加载ApplicationContext 的ApplicationContext 。

TestContextManager

其是Spring TestContext Framework主要切点,其管理了一个TestContext ,并在测试执行切点,为注册的TestExecutionListener 提供信号事件。切点包括: - 特定测试框架的所有的类before或所有方法的before之前 - 测试实例post-processing - 特定测试框架的每个before或每个方法的before之前 - 特定测试框架的每个after或每个方法的after之后 - 特定测试框架的所有after或所有方法的after之后

TestExecutionListener

TestExecutionListener 定义了响应TestContextManager 发出的测试执行事件的接口。阅读Section 15.5.3, “TestExecutionListener configuration”了解详情。

Context Loaders

ContextLoader 自Spring 2.5引入,是为了Spring TestContext Framework中一个集成测试加载ApplicationContext 定义的策略接口。SmartContextLoader接口的实现,提供了对带注释的类、活动bean定义Profile、测试属性源、上下文层次结构和WebApplicationContext的支持。 SmartContextLoader 是ContextLoader 接口的扩展,自Spring 3.1引入。SmartContextLoader 定义的SPI取代了ContextLoader的SPI. 值得关注的是,SmartContextLoader 可以配置资源locations,注解类,上下文initializers。此外,SmartContextLoader 还可以在上下文中定义活跃bean的profiles和测试属性源。 spring提供了以下的实现: - DelegatingSmartContextLoader。两个默认加载器的其中一个。按照测试类的配置,默认位置配置或默认配置类的配置信息,使用AnnotationConfigContextLoader,GenericXmlContextLoader或GenericGroovyXmlContextLoader来完成真正的加载工作。只有当Groovy在classpath下的时候,才会被支持。 - WebDelegatingSmartContextLoader。另外一个默认加载器。和上面的类似,具体工作由AnnotationConfigWebContextLoader,GenericXmlWebContextLoader或GenericGroovyXmlWebContextLoader 实现。web ContextLoader 只有在测试类上添加了@WebAppConfiguration注解,才可以使用。只有当Groovy在classpath下的时候,才会被支持。 - AnnotationConfigContextLoader。从注解注释的类中加载标准ApplicationContext 。 - AnnotationConfigWebContextLoader。从注解注释的类中加载WebApplicationContext 。 - GenericGroovyXmlContextLoader。从配置文件加载标准ApplicationContext 。配置文件可以是Groovy 脚本或XML配置文件。 - GenericGroovyXmlWebContextLoader。从配置文件加载WebApplicationContext 。配置文件可以是Groovy 脚本或XML配置文件。 - GenericXmlContextLoader。从XML 配置加载标准ApplicationContext 。 - GenericXmlWebContextLoader。从XML 配置加载WebApplicationContext 。 - GenericPropertiesContextLoader。从Java 配置文件,加载标准ApplicationContext 。

15.5.2 启动TestContext framework

Spring TestContext Framework内部的默认配置已经可以满足所有的一般情况。然而,有些开发团队或者第三方架构想更改默认的ContextLoader,实现自定义的ContextLoader或者ContextCache,比如将ContextCustomizerFactory 和TestExecutionListener 的自定义实现作为参数设置。类似于这种控制TestContext 框架的操作,Spring提供了加载策略。 TestContextBootstrapper 定义了引导TestContext 框架的SPI. TestContextBootstrapper 被TestContextManager 用来为测试加载TestExecutionListener 实现,以及创建TestContext。自定义引导策略可以通过@BootstrapWith配置到测试类上,直接使用和通过元注解使用都可以。如果一个引导程序没有通过@BootstrapWith配置,则会默认使用DefaultTestContextBootstrapper 或者WebTestContextBootstrapper。具体使用哪个,依赖于@WebAppConfiguration的配置。 由于TestContextBootstrapper SPI将会基于新的需求而改动,实现者最好不要直接实现接口,而是扩展AbstractTestContextBootstrapper或者它的子类。

15.5.3 TestExecutionListener配置

spring提供了TestExecutionListener 的默认加载实现类,且按照以下的顺序加载: - ServletTestExecutionListener。为Servlet API mocks 配置Servlet API mocks。 - DirtiesContextBeforeModesTestExecutionListener。为before方法处理@DirtiesContext注解。 - DependencyInjectionTestExecutionListener。为测试提供依赖注入。 - DirtiesContextTestExecutionListener。为after方法处理@DirtiesContext注解。 - TransactionalTestExecutionListener。提供事物的测试执行,默认回滚。 - SqlScriptsTestExecutionListener。执行通过@Sql配置的sql脚本

注册自定义TestExecutionListeners

自定义TestExecutionListeners 可以通过 @TestExecutionListeners注册到测试类和父类中。可以阅读 annotation support和@TestExecutionListeners的javadoc获得详细信息。

默认TestExecutionListeners的自动发现

在少量测试用例时,用户可以通过@TestExecutionListeners来注册自定义TestExecutionListeners;但是,在测试集中使用就会显得笨重。从Spring Framework 4.1开始, 通过SpringFactoriesLoader 机制实现的自动发现默认TestExecutionListener解决了这个问题。 spring-test 模块在org.springframework.test.context.TestExecutionListener包下定义了所有的默认核心TestExecutionListeners ,key存放在META-INF/spring.factories文件中。第三方框架和开发者可以在自己工程的META-INF/spring.factories文件中定义自定义TestExecutionListeners 作为默认监听器。

TestExecutionListeners排序

当TestContext框架通过SpringFactoriesLoader 机制发现默认TestExecutionListeners ,实例化的监听器将会被AnnotationAwareOrderComparator按照Ordered 接口或者@Order注解 进行排序。Spring提供的AbstractTestExecutionListener 和所有默认的TestExecutionListeners ,都实现了Order接口,并赋予了合适的顺序值。第三方架构和开发者也可以通过Order接口或者@Order为自定义的TestExecutionListeners 设置合适的顺序值。可以通过阅读javadoc获得默认TestExecutionListeners 的顺序值。

合并TestExecutionListeners

如果一个自定义的TestExecutionListener 通过@TestExecutionListeners注册,默认的监听器将不会被注册。在大多数场景下,这将有利于开发者除了自定义监听器之外,定义所有的监听器。下面是这种风格的配置示范:

@ContextConfiguration @TestExecutionListeners({ MyCustomTestExecutionListener.class, ServletTestExecutionListener.class, DirtiesContextBeforeModesTestExecutionListener.class, DependencyInjectionTestExecutionListener.class, DirtiesContextTestExecutionListener.class, TransactionalTestExecutionListener.class, SqlScriptsTestExecutionListener.class }) public class MyTest { // class body... }

这种方式对于开发者的挑战是,开发者需要明确知道所有默认加载的监听器。但是默认的监听器会在不同的Spring释放版本中发生变化,比如SqlScriptsTestExecutionListener 在4.1中引入,DirtiesContextBeforeModesTestExecutionListener 在4.2中引入。此外,第三方框架,如Spring Security注册自定义的TestExecutionListeners 。 为了避免开发者必须知道所有默认监听器,@TestExecutionListeners 的属性mergeMode 可以设置成MergeMode.MERGE_WITH_DEFAULTS。MERGE_WITH_DEFAULTS 表示本地声明的监听器将会和默认监听器合并。合并规则将冗余的监听器删除,通过AnnotationAwareOrderComparator 进行排序。如果一个监听器实现了Order接口或配置了@Order,则会影响它的排序位置;否则本地实现的监听器会附加到默认监听器后边。 比如说,MyCustomTestExecutionListener 类配置了顺序值500,比ServletTestExecutionListener 的顺序值1000少,MyCustomTestExecutionListener 将会排在ServletTestExecutionListener之前。先前的例子应该修改为:

@ContextConfiguration @TestExecutionListeners( listeners = MyCustomTestExecutionListener.class, mergeMode = MERGE_WITH_DEFAULTS ) public class MyTest { // class body... }

15.5.4 Context管理

每个TestContext都为测试实例提供了上下文管理和缓存。测试实例不自动接收配置的ApplicationContext。然而如果一个测试类实现了ApplicationContextAware 接口,将为测试实例提供ApplicationContext 的引用。注意AbstractJUnit4SpringContextTests 和AbstractTestNGSpringContextTests 实现了ApplicationContextAware,因此可以自动接收到ApplicationContext 。 除了实现ApplicationContextAware 接口,你可以在通过属性或方法注解@Autowired来注入context 。比如:

@RunWith(SpringRunner.class) @ContextConfiguration public class MyTest { @Autowired private ApplicationContext applicationContext; // class body... }

相似的,如果你的测试用例配置为WebApplicationContext,你可以这么配置:

@RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration public class MyWebAppTest { @Autowired private WebApplicationContext wac; // class body... }

@Autowired 注入是通过默认监听器DependencyInjectionTestExecutionListener 实现的。Section 15.5.5, “Dependency injection of test fixtures” 使用TestContext 框架的测试用例不需要扩展任何特定类或实现接口来配置。只需要在类级别定义@ContextConfiguration就可以实现配置。如果你的测试用例没有声明资源locations或者注解类,ContextLoader 将会通过默认位置下的默认文件,或默认的配置类来加载上下文。除了资源location和注解的类,context也可以通过应用程序上下文initializers配置。 余下的部分将介绍如何通过@ContextConfiguration中描述的XML配置文件,Groovy脚本,注解(通常是@Configuration)类或者context initializers 来配置ApplicationContext。另外,你可以实现和配置自己的SmartContextLoader 。

通过XML配置Context

为了使用XML配置文件来加载ApplicationContext ,需要在测试类上配置@ContextConfiguration,并在属性locations中配置XML配置文件的位置列表。一个直接文件或相对路径,比如locations,会被认为是定义测试类的classpath下的文件。如果一个路径以“/”开头,会被认为是绝对路径,比如”/org/example/config.xml”。还可以用资源URL的方式配置:classpath:, file:, http:开头。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from "/app-config.xml" and // "/test-config.xml" in the root of the classpath @ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) public class MyTest { // class body... }

@ContextConfiguration支持locations的别名value。因此,如果不需要配置其他的@ContextConfiguration属性,可以省去locations使用简洁的配置方式:

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

如果同时配置了@ContextConfiguration的locations 和value 属性,TestContext 会试着寻找默认XML资源位置。GenericXmlContextLoader 和GenericXmlWebContextLoader 会根据测试类的名字查找默认配置。如果你的测试类名字为com.example.MyTest,GenericXmlContextLoader 将会加载”classpath:com/example/MyTest-context.xml”。

package com.example; @RunWith(SpringRunner.class) // ApplicationContext will be loaded from // "classpath:com/example/MyTest-context.xml" @ContextConfiguration public class MyTest { // class body... }

通过Groovy 脚本配置

通过Groovy脚本配加载ApplicationContext ,脚本需要使用Groovy Bean Definition DSL.和XML类似,需要使用@ContextConfiguration注释测试类,并通过属性locations或value 配置脚本位置。 如果classpath下有Groovy ,Spring TestContext框架会自动支持Groovy。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from "/AppConfig.groovy" and // "/TestConfig.groovy" in the root of the classpath @ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) public class MyTest { // class body... }

如果同时定义了locations和value属性,TestContext会加载默认配置文件。GenericGroovyXmlContextLoader 和GenericGroovyXmlWebContextLoader 会通过类的名字加载文件。比如你的类名为com.example.MyTest,则会加载”classpath:com/example/MyTestContext.groovy”。

package com.example; @RunWith(SpringRunner.class) // ApplicationContext will be loaded from // "classpath:com/example/MyTestContext.groovy" @ContextConfiguration public class MyTest { // class body... }

XML和Groovy配置都可以同时设置@ContextConfiguration的locations和value属性。如果配置的文件以.xml结尾,则会使用XmlBeanDefinitionReader加载;否则将会使用GroovyBeanDefinitionReader。 下面的demo展示了怎样同时使用两者:

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from // "/app-config.xml" and "/TestConfig.groovy" @ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" }) public class MyTest { // class body... }

通过注解配置

使用注解来配置ApplicationContext ,在测试类上使用@ContextConfiguration,并通过classes 属性配置其他的注解配置的类。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from AppConfig and TestConfig @ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) public class MyTest { // class body... }

注解配置的类包含以下几种: - @Configuration配置的类 - Spring的一个bean(@Component, @Service, @Repository等配置的类) - JSR-330标准的类,通过javax.inject的注解配置 - 其他包含@Bean方法的类。 可以阅读@Configuration和@Bean的javadoc获得详细信息。尤其要关注@Bean Lite Mode。 如果忽略了@ContextConfiguration的classes属性,TestContext框架将会试着加载默认配置类。AnnotationConfigContextLoader 和AnnotationConfigWebContextLoader 将会探测测试类中满足@Configuration要求的内部静态类。在下面的实例中,OrderServiceTest 测试类定义了一个内部静态类Config ,框架将会默认使用Config来加载ApplicationContext 。注意,配置类的名字是任意的。另外,一个测试类可以包含多个用于配置的内部静态测试类。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from the // static nested Config class @ContextConfiguration public class OrderServiceTest { @Configuration static class Config { // this bean will be injected into the OrderServiceTest class @Bean public OrderService orderService() { OrderService orderService = new OrderServiceImpl(); // set properties, etc. return orderService; } } @Autowired private OrderService orderService; @Test public void testOrderService() { // test the orderService } }

混合使用XML, Groovy scripts, annotated classes

有些时候我们需要混合使用XML, Groovy scripts, annotated classes来加载ApplicationContext 。比如说,在生产环境使用XML,在测试环境中使用@Configuration配置测试专用的spring bean。反之亦然。 进一步来说,一些第三方测试框架(比如Spring Boot)对使用不同类型的配置文件(e.g., XML configuration files, Groovy scripts, and @Configuration classes)加载ApplicationContext 提供了一级支持。Spring Framework最开始不支持这种标准的部署。因此,spring-test中的大部分的SmartContextLoader 实现,在一个测试上下文中只支持一种配置方式;然而这并不是意味着你不能同时使用多个。GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader 同时支持XML 和Groovy脚本配置。进一步来说,第三方框架通过同时配置locations ,classes 实现混合使用的支持。如果使用TestContext,你有如下的选择: 如果想使用资源位置 (e.g., XML or Groovy)和@Configuration类进行配置,你可以使用一种作为切入,并包含或引入其他的配置。比如在XML或Groovy脚本中通过组件扫描或定义为普通bean的方式引入@Configuration类;反之,在@Configuration配置的类中,通过@ImportResource引入XML配置文件或Groovy脚本。注意,这和生产环境的配置支持是相似的:生产环境中可以定义一系列的XML和Groovy脚本文件,或@Configuration类,进行ApplicationContext 加载配置。当然,也可以include或import其他类型的配置文件。

通过上下文initializers配置Context

可以通过@ContextConfiguration配置initializers来加载ApplicationContext 。@ContextConfiguration的属性initializers 可以配置ApplicationContextInitializer实现类的数组。配置的上下文initializers将会用来加载ConfigurableApplicationContext 。注意initializer 支持的ConfigurableApplicationContext 实例必须和SmartContextLoader 创建的ApplicationContext 类型相匹配(通常是GenericApplicationContext)。另外,initializers 的调用顺序,依赖于实现Spring接口Ordered 或者@Order注释,或者标准的@Priority注释提供的值。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from TestConfig // and initialized by TestAppCtxInitializer @ContextConfiguration( classes = TestConfig.class, initializers = TestAppCtxInitializer.class) public class MyTest { // class body... }

我们可以在@ContextConfiguration中完全忽略掉XML,Groovy和注解注释的类的配置,完全通过ApplicationContextInitializer 来加载和注册Bean。比如以编程方式从XML或配置类加载bean的定义。

@RunWith(SpringRunner.class) // ApplicationContext will be initialized by EntireAppInitializer // which presumably registers beans in the context @ContextConfiguration(initializers = EntireAppInitializer.class) public class MyTest { // class body... }

Context 配置继承

@ContextConfiguration通过属性inheritLocations 和inheritInitializers 控制是否从父类继承资源文件、注解的类的配置和initializers 的配置。两个属性的默认值为true。也就是说测试类会继承相关的配置信息。明确的说,当前测试类对于资源文件,注解注释的类以及initializers的配置,会追加到父类的配置信息之后。因此,子类有是否继承相关配置的选项。 如果inheritLocations 和inheritInitializers 被设置成false,子类的locations、annotated classes和initializers配置将会替换父类的配置。 在下面实例中使用的XML locations配置,ExtendedTest 将会从”base-config.xml” 和”extended-config.xml”加载ApplicationContext ,并按照这个顺序加载。”extended-config.xml”中定义的bean将会覆盖 “base-config.xml”中的定义。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from "/base-config.xml" // in the root of the classpath @ContextConfiguration("/base-config.xml") public class BaseTest { // class body... } // ApplicationContext will be loaded from "/base-config.xml" and // "/extended-config.xml" in the root of the classpath @ContextConfiguration("/extended-config.xml") public class ExtendedTest extends BaseTest { // class body... }

相似的,下面的实例使用了annotated classes。ExtendedConfig 会覆盖BaseConfig的bean定义。

@RunWith(SpringRunner.class) // ApplicationContext will be loaded from BaseConfig @ContextConfiguration(classes = BaseConfig.class) public class BaseTest { // class body... } // ApplicationContext will be loaded from BaseConfig and ExtendedConfig @ContextConfiguration(classes = ExtendedConfig.class) public class ExtendedTest extends BaseTest { // class body... }

在下面的实例中,ExtendedTest 将会使用BaseInitializer 和and ExtendedInitializer加载ApplicationContext 。但是运行的顺序依赖于Ordered 接口,@Order或@Priority的顺序定义。

@RunWith(SpringRunner.class) // ApplicationContext will be initialized by BaseInitializer @ContextConfiguration(initializers = BaseInitializer.class) public class BaseTest { // class body... } // ApplicationContext will be initialized by BaseInitializer // and ExtendedInitializer @ContextConfiguration(initializers = ExtendedInitializer.class) public class ExtendedTest extends BaseTest { // class body... }

使用环境profiles配置Context

Spring 3.1对于环境变量和profiles 提供了一级支持。集成测试可以配置成为不同的测试场景激活特定的bean定义。这需要在测试类上配置@ActiveProfiles,从而在加载ApplicationContext 的时候提供一组需要激活的profiles。 @ActiveProfiles可以和新的SmartContextLoader SPI的任意实现配合,但是@ActiveProfiles没有被老的ContextLoader SPI支持。 让我们看一下使用XML配置和@Configuration类的实例

<!-- 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 profile="default"> <jdbc:embedded-database id="dataSource"> <jdbc:script location="classpath:com/bank/config/sql/schema.sql"/> </jdbc:embedded-database> </beans> </beans> package com.bank.service; @RunWith(SpringRunner.class) // ApplicationContext will be loaded from "classpath:/app-config.xml" @ContextConfiguration("/app-config.xml") @ActiveProfiles("dev") public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService } }

当TransferServiceTest 运行,他的ApplicationContext 将会通过classpath根目录下的app-config.xml配置加载。如果查看了app-config.xml将会发现bean accountRepository 依赖于dataSource;然而dataSource 没有作为顶级bean定义。相反,dataSource 被定义了三次:production Profile,dev profile以及默认profile。 TransferServiceTest 类注释了@ActiveProfiles(“dev”),Spring TestContext Framework只对profile为{“dev”}的内容加载到ApplicationContext 。因此,框架会加载一个填充了测试数据的嵌入式数据库,accountRepository bean会引用开发的DataSource。这就是我们在集成测试中需要的。 有的时候需要将bean划分到default profile。当没有配置profile 时,没有配置profile的bean才会被加载。这可以用于为应用程序的默认状态定义fallback bean。比如说,你可能为dev和production profile分别定义了数据源,当时当两者都没有定义时,定义一个内存数据库。 下面的代码列表展示了怎样实现相同的配置和集成测试,使用@Configuration类替代XML

@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(); } } @Configuration @Profile("production") public class JndiDataConfig { @Bean(destroyMethod="") public DataSource dataSource() throws Exception { Context ctx = new InitialContext(); return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource"); } } @Configuration @Profile("default") public class DefaultDataConfig { @Bean public DataSource dataSource() { return new EmbeddedDatabaseBuilder() .setType(EmbeddedDatabaseType.HSQL) .addScript("classpath:com/bank/config/sql/schema.sql") .build(); } } @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(); } } package com.bank.service; @RunWith(SpringRunner.class) @ContextConfiguration(classes = { TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class, DefaultDataConfig.class}) @ActiveProfiles("dev") public class TransferServiceTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService } }

在转变中,我们将XML配置拆分成四个独立的@Configuration 类: - TransferServiceConfig:需要dataSource ,通过@Autowired注释 - StandaloneDataConfig:为嵌入式数据库定义了dataSource 。适用于开发者测试。 - JndiDataConfig:生产环境中,从JNDI 接受dataSource。 - DefaultDataConfig:当没有profile时,定义一个默认的嵌入式数据库dataSource 。 参考XML-based 的配置,我们依旧为TransferServiceTest添加注解@ActiveProfiles(“dev”)。但是这次我们为四个配置类都添加了@ContextConfiguration注解。测试类的主体没有变化。 在常理认为,一组profile可以在一个项目中的多个测试类中使用。因此为了避免@ActiveProfiles重复使用,我们在基类中配置一次,子类会默认继承基类的配置。在下面的列子中,@ActiveProfiles的配置迁移到抽象父类AbstractIntegrationTest。

package com.bank.service; @RunWith(SpringRunner.class) @ContextConfiguration(classes = { TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.class, DefaultDataConfig.class}) @ActiveProfiles("dev") public abstract class AbstractIntegrationTest { } package com.bank.service; // "dev" profile inherited from superclass public class TransferServiceTest extends AbstractIntegrationTest { @Autowired private TransferService transferService; @Test public void testTransferService() { // test the transferService } }

@ActiveProfiles也支持属性inheritProfiles ,从而避免profile的继承。

package com.bank.service; // "dev" profile overridden with "production" @ActiveProfiles(profiles = "production", inheritProfiles = false) public class ProductionTransferServiceTest extends AbstractIntegrationTest { // test body }

进一步说,profile可以用编程式测试替代声明式测试-例如,依赖于: - 当前操作系统 - 是否在持续集成构建环境进行测试 - 确定的环境变量 - 自定义类级别注解 - 等等

为了实现编程式的bean定义,需要实现自定义的ActiveProfilesResolver 并将其设置到@ActiveProfiles的resolver 属性。下面的实例展示了怎样实现和注册自定义的OperatingSystemActiveProfilesResolver。可以通过javadoc获得进一步了解。

package com.bank.service; // "dev" profile overridden programmatically via a custom resolver @ActiveProfiles( resolver = OperatingSystemActiveProfilesResolver.class, inheritProfiles = false) public class TransferServiceTest extends AbstractIntegrationTest { // test body } package com.bank.service.test; public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver { @Override String[] resolve(Class<?> testClass) { String profile = ...; // determine the value of profile based on the operating system return new String[] {profile}; } }

测试属性源配置Context

SPring 3.1就在框架级别对环境中配置属性源层级结构的一级支持,直到Spring4.1集成测试可以使用测试专用的属性源配置测试用例。与使用在@Configuration类的注解@PropertySource形成鲜明对比,@TestPropertySource可以用于测试类上,为测试属性文件或内置属性声明资源位置。这些测试属性源将会在Environment 中被添加到PropertySources ,从而作用于ApplicationContext 加载。 @TestPropertySource可以和SmartContextLoader SPI的任意实现配合使用,但是老版本的ContextLoader SPI不支持@TestPropertySource。 SmartContextLoader 的实现类通过MergedContextConfiguration的getPropertySourceLocations()和getPropertySourceProperties()两个方法获取合并后的属性源值。

声明测试属性源

测试属性文件可以通过@TestPropertySource的locations和value属性配置,这些将会在之后的实例中展示。 传统的和XML格式的文件都被支持,比如”classpath:/com/example/test.properties”或”file:///path/to/file.xml”。 每个路径都被spring认为是一个Resource。一个直接的path-比如”test.properties”-会被认为是classpath资源,定位到测试类的包路径对应的文件夹路径下。如果路径以斜杠开头,则认为是绝对路径,比如”/org/example/test.xml”。如果路径是URL(以classpath:, file:, http:等开头),则会采用对应的协议加载。不支持资源location通配符(比如*/.properties):每个location都必须指向一个.properties或.xml资源。

@ContextConfiguration @TestPropertySource("/test.properties") public class MyIntegrationTests { // class body... }

键值对形式的内置属性可以通过@TestPropertySource 的properties属性配置,如下面的例子。所有的键值对将会作为一个单独的测试PropertySource 添加到Environment 中,且此属性有最高优先级。 键值对支持的语法和java properties文件中的语法相同: - “key=value” - “key:value” - “key value”

@ContextConfiguration @TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) public class MyIntegrationTests { // class body... }

默认properties文件探测

如果@TestPropertySource没有任何配置(没有指定locations和properties的值),框架将会自动探测配置了注解类的默认属性文件。比如说,一个配置了注解的测试类com.example.MyTest,对应的默认属性文件为”classpath:com/example/MyTest.properties”。如果找不到默认文件,那么将会抛出异常IllegalStateException 。

优先级 和操作系统环境变量、java系统配置以及通过@PropertySource或编程添加的属性相比,测试属性源具有更高的优先级。因此测试属性源可以用来选择性的覆盖系统或应用程序中的属性。进一步说,内置属性和资源位置加载的属性相比,具有更高优先级。 在下面的实例中,timezone和port属性和其他定义在”/test.properties”中的属性一样,将会覆盖在系统或应用程序中定义的具有相同名字的属性源。进一步说,如果”/test.properties”定义了timezone和port,那么将会被properties属性定义的属性覆盖。

@ContextConfiguration @TestPropertySource( locations = "/test.properties", properties = {"timezone = GMT", "port: 4242"} ) public class MyIntegrationTests { // class body... }

继承和覆盖测试属性

@TestPropertySource支持inheritLocations和inheritProperties属性,表示是否继承父类的资源文件位置配置和内置属性配置。默认的值未true。这意味着测试类会继承locations和内置属性的配置。明确的说,测试类的locations 和内置属性配置会附加到父类的属性配置后。因此,子类可以选择是否扩展locations和内置属性。注意,之后配置的属性会覆盖之前相同名称的配置。另外,之前描述的优先级策略也适用于测试属性源。 如果@TestPropertySource的inheritLocations和inheritProperties属性设置成false,测试类的配置将会替代父类的配置定义。 在下面的例子中,BaseTest的ApplicationContext将会只使用“base.properties”文件加载。相反,ExtendedTest将会使用”base.propertires“和”extended.properties“两个文件加载。

@TestPropertySource("base.properties") @ContextConfiguration public class BaseTest { // ... } @TestPropertySource("extended.properties") @ContextConfiguration public class ExtendedTest extends BaseTest { // ... }

在下面的例子中,BaseTest将会使用内置的Key1属性配置。相对应的,ExtendedTest将会使用内置的key1和key2属性配置。

@TestPropertySource(properties = "key1 = value1") @ContextConfiguration public class BaseTest { // ... } @TestPropertySource(properties = "key2 = value2") @ContextConfiguration public class ExtendedTest extends BaseTest { // ... }

加载WebApplicationContext

Spring3.2开始支持集成测试加载WebApplicationContext。为了引导TestContext加载WebApplicationContext而不是加载ApplicationContext,需要在测试类上配置@WebAPPConfiguration。 测试类上配置的@WebAPPConfiguration将会告知TestContext框架(TestContext framework (TCF))应该加载WebApplicationContext(WAC) 。TCF在后台确认MockServletContext被创建,并提供给测试的WAC。默认下,MockServletContext的base路径为 “src/main/webapp”。这可以理解为你的JVM为根(通常是项目的路径)的一个相对路径。如果熟悉maven工程的web应用的目录结构,你就会知道”src/main/webapp”是WAR的默认根路径。如果希望使用classpath来指向资源根路径,可以以classpath:开头。 注意,Spring测试对于WebApplicationContext的支持实在标准的ApplicationContext基础上实现的。你可以使用XML配置文件,Groovy脚本或@Configuration,@ContextConfiguration注解实现配置。当然也可以自由的使用任何测试注解,比如@ActiveProfiles, @TestExecutionListeners, @Sql, @Rollback等等。 下面的demo演示了各种方法加载WebApplicationContext。

约定

@RunWith(SpringRunner.class) // defaults to "file:src/main/webapp" @WebAppConfiguration // detects "WacTests-context.xml" in same package // or static nested @Configuration class @ContextConfiguration public class WacTests { //... }

上面的demo延时了TestContext框架的约定配置。如果@WebAppConfiguration没有配置任何资源路径,默认的资源路径为”file:src/main/webapp”。相似的,如果@ContextConfiguration没有任何资源locations,注解注释的类,或者initalizers配置,Spring会按照约定来探测配置。(WacTests包路径相同路径下的 “WacTests-context.xml”,或者静态内置@Configuration配置的类)

默认资源语法

@RunWith(SpringRunner.class) // file system resource @WebAppConfiguration("webapp") // classpath resource @ContextConfiguration("/spring/test-servlet-config.xml") public class WacTests { //... }

实例展示了怎样通过@WebAPPConfiguration配置资源根路径,且通过@ContextConfiguration配置XML资源位置。要记住很重要的一点:两个注解的路径配置语法是不同的。默认情况,@WebAPPConfiguration的资源路径是文件系统路径;然而@ContextConfiguration是classpath路径。

明确资源的语法

@RunWith(SpringRunner.class) // classpath resource @WebAppConfiguration("classpath:test-web-resources") // file system resource @ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml") public class WacTests { //... }

在第三个例子中,两个注解中配置的信息,会覆盖默认资源信息。 为了提供广泛的web测试,Spring3.2提供了ServletTestExecutionListener,并在默认情况下是激活的。当进行WebAPPlicationContext测试时,TestExecutionListener通过Spring Web的RequestContextHolder 在每个测试方法之前建立了默认的thread-local 状态,同时也通过@WebAppConfiguration的配置,创建了一个MockHttpServletRequest, MockHttpServletResponse, 和 ServletWebRequest。ServletTestExecutionListener同时确保MOckHttpServletResponse和ServletWebRequest会注入到测试实例中,并在测试完成的时候清空thread-local状态。 一旦测试加载了WebApplicationContext,你会发现需要和web mock互动-比如,在web组件调用后构建测试点或执行assert。下面的例子演示了哪些mock可以autowired注入到测试实例中。注意WebApplicationContext和MockServletContext都是测试集缓存的;然而其他的mock是每个测试方法通过ServletTestExecutionListener管理自己的。

* 注入mocks*

@WebAppConfiguration @ContextConfiguration public class WacTests { @Autowired WebApplicationContext wac; // cached @Autowired MockServletContext servletContext; // cached @Autowired MockHttpSession session; @Autowired MockHttpServletRequest request; @Autowired MockHttpServletResponse response; @Autowired ServletWebRequest webRequest; //... }

Context缓存

一旦TestContext框架为一个测试加载了一个ApplicationContext或WebApplicationContext,这个context会被缓存,并在此测试集中的相同配置unique的测试中重用。为了理解缓存是怎样工作的,我们需要理解unique和test suite。 一个ApplicationContext可以使用配置参数结合而成的信息唯一标识。因此,可以通过配置参数结合而生成的key来缓存context。TestContext架构通过下面的参数来构建缓存key: - locations (from @ContextConfiguration) - classes (from @ContextConfiguration) - contextInitializerClasses (from @ContextConfiguration) - contextCustomizers (from ContextCustomizerFactory) - contextLoader (from @ContextConfiguration) - parent (from @ContextHierarchy) - activeProfiles (from @ActiveProfiles) - propertySourceLocations (from @TestPropertySource) - propertySourceProperties (from @TestPropertySource) - resourceBasePath (from @WebAppConfiguration)

举个例子,如果TestClassA的注解@ContextConfiguration设定locations={“app-config.xml”, “test-config.xml”},TestContext框架将会加载对应的ApplicationContext并将context存储到静态context缓存中,key是依据这些地址生成。如果TestClassB也定义了locations={“app-config.xml”, “test-config.xml”},但是可没有定义为@WebAppConfiguration(那么将会使用不同的ContextLoader,活跃的profiles,context initializers,测试属性源以及上级context),所以两个测试用例会使用相同的ApplicationContext。这意味着只会构建一次应用程序的context(每个测试集中),测试执行会更快。

Spring TestContext架构采用静态cache存储应用程序context。这意味着context存储在一个静态变量中。换句话说,如果测试在独立的进程中运行,静态cache会在各个test执行期间清空,这会导致缓存机制失效。

为了使用缓存机制,所有的测试必须在同一个进程,或测试集中。这会通过IDE中将所有的测试作为一个组来实现。相似的,当测试用例通过构建框架执行,比如ant,maven,gradle,我们需要确认框架不会再测试之间执行fork。比如,如果maven surefire插件的forkMode设置成always或pertest,TestContext测试框架将不能再测试类之间缓存应用程序context,构建过程将会变得很慢。

自从Spring Framework 4.3开始,context缓存的默认最大值为32.当缓存的数量达到最大值,框架将会执行LRU策略清除数据并关闭context。可以通过命令行或者构建脚本设置最大值,更改JVM系统属性spring.test.context.cache.maxSize。另外一种方式为,通过SpringProperties API设置此属性的值。 由于在一个测试集中记载大量的应用程序context,会导致测试集花费不必要的长时间来执行。通常知道有多少个context被加载和缓存是十分必要的。为了查看context缓存的统计信息,可以将org.springframework.test.context.cache的日志级别设成DEBUG。 在一些不常见的场景中,测试污染了应用程序context,需要重新加载-比如修改了一个bean的定义或一个应用程序对象,你可以通过@DirtiesContext配置你的测试类或测试方法。(Section 15.4.1, “Spring Testing Annotations”中有详细介绍)。这会使Spring从缓存中删除Context,并在下一个test执行之前重新构建应用程序Context。注意,DirtiesContextBeforeModesTestExecutionListener 实现了对于注解@DirtiesContext的支持,且此listener是默认激活的。

context层级

当写依赖于Spring ApplicationContext的集成测试,一般情况下一个context已经完全可以满足测试;但是在有些时候为测试维护一个ApplicationContext层级是必要的。比如,如果正在开发一个Spring MVC应用程序,一般需要通过Spring的ContextLoaderListener加载一个根WebApplicationContext,通过Spring的DispatcherServlet加载一个子WebApplicationContext。这会产生一个父子层级结构,结构中共享的组件和基础配置在root context定义,而将web特制的组件定义到子context中。我们可以在Spring Batch的应用程序中找到另一个例子:parent context提供了共享批量架构的配置,子context为特定的批处理任务提供配置。 自从Spring Framework 3.2.2开始,在写集成测试的时候,我们可以通过@ContextHierarchy定义context的层级,@ContextHierarchy可以用于单独的测试类或者在一个测试类层级中。如果一个context层级在拥有多个类的测试类层级中定义,那么这个context层级可以合并或覆盖已有层级中的context。当为层级中的指定一级合并配置,配置资源的类型必须一致的;否则,采用不同资源类型将会认为是不同层级。 下面这个使用Junit4的例子说明了需要context层级的集成测试的流行配置语法。 ControllerIntegrationTests 是Spring MVC应用程序一种典型的集成测试语法,定义了两层的context:一层是root WebApplicationContext(使用TestAppConfig @Configuration类),另一层是dispatcher servlet WebApplicationContext (使用WebConfig @Configuration类加载)。autowired到测试实例的是子WebApplicationContext 中的一个。(最低层级的context)

@RunWith(SpringRunner.class) @WebAppConfiguration @ContextHierarchy({ @ContextConfiguration(classes = TestAppConfig.class), @ContextConfiguration(classes = WebConfig.class) }) public class ControllerIntegrationTests { @Autowired private WebApplicationContext wac; // ... }

下面的测试类,在一个测试类层级中定义了context。AbstractWebTests 在Spring的web 应用程序中声明了根WebApplicationContext 的配置。注意,AbstractWebTests 没有声明@ContextHierarchy;因此,AbstractWebTests的子类可以加入到一个层级context中,或者仅仅按照@ContextConfiguration标准语法配置。SoapWebServiceTests 和RestWebServiceTests 都是从AbstractWebTests 扩展,并通过@ContextConfiguration定义context层级。结果就是三个应用程序context将会被加载(每个都@ContextConfiguration),AbstractWebTests 配置的context将会作为其他子类加载的context的parent。

@RunWith(SpringRunner.class) @WebAppConfiguration @ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml") public abstract class AbstractWebTests {} @ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml") public class SoapWebServiceTests extends AbstractWebTests {} @ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml") public class RestWebServiceTests extends AbstractWebTests {}

下面的类展示了怎样使用命名的层级,从而将context插入到context层级的指定层。BaseTests 定义了两级:parent和child。ExtendedTests扩展了BaseTests,并应道SPring TestContext Framework为child层级合并配置信息。@ContextConfiguration中name属性都设成“child”,从而确认合并的层级。三个应用程序的contex会被加载:一个是”/app-config.xml”, 一个是”/user-config.xml”, 一个是 {“/user-config.xml”, “/order-config.xml”}。参考之前的实例,通过”/app-config.xml”加载的应用程序Context将作为”/user-config.xml”和{“/user-config.xml”, “/order-config.xml”}的parent。

@RunWith(SpringRunner.class) @ContextHierarchy({ @ContextConfiguration(name = "parent", locations = "/app-config.xml"), @ContextConfiguration(name = "child", locations = "/user-config.xml") }) public class BaseTests {} @ContextHierarchy( @ContextConfiguration(name = "child", locations = "/order-config.xml") ) public class ExtendedTests extends BaseTests {}

和上一个例子相反,这个例子演示了怎样通过@ContextConfiguration的inheritLocations= false来实现覆盖给定名字的context层级。ExtendedTests的应用程序context只会从”/test-user-config.xml”加载,他的父节点将会通过”/app-config.xml”加载。

@RunWith(SpringRunner.class) @ContextHierarchy({ @ContextConfiguration(name = "parent", locations = "/app-config.xml"), @ContextConfiguration(name = "child", locations = "/user-config.xml") }) public class BaseTests {} @ContextHierarchy( @ContextConfiguration( name = "child", locations = "/test-user-config.xml", inheritLocations = false )) public class ExtendedTests extends BaseTests {}

如果@DritiesContext配置的测试的context是一个层级的一部分,那么可以使用hierarchyMode可以用来控制context缓存的清空规则。可以通过 Spring Testing Annotations和@DirtiesContext的javadoc获得更多信息。

转载请注明原文地址: https://www.6miu.com/read-1750331.html

最新回复(0)