Spring 测试(第一部分)
1.Spring Testing介绍
测试是企业软件开发的一个组成部分。本章重点讨论IoC原则为单元测试 unit testing增加的价值,以及Spring框架对集成测试 integration testing.的支持带来的好处。(在企业中对测试的全面处理超出了本参考手册的范围。)
2.Unit Testing
与传统的Java EE开发相比,依赖项注入应该使您的代码更少地依赖于容器。组成应用程序的pojo应该可以在JUnit或TestNG测试中进行测试,使用新的操作符实例化对象,而不需要Spring或任何其他容器。您可以使用模拟对象mock objects(与其他有价值的测试技术一起使用)来独立地测试您的代码。如果您遵循Spring的体系结构建议,那么代码库的清晰分层和组件化将简化单元测试。例如,您可以通过存根或模仿DAO或存储库接口来测试服务层对象,而不需要在运行单元测试时访问持久数据。
真正的单元测试通常运行得非常快,因为不需要设置运行时基础设施。将真正的单元测试作为开发方法的一部分可以提高您的生产力。您可能不需要测试章节的这一部分来帮助您为基于ioci的应用程序编写有效的单元测试。但是,对于某些单元测试场景,Spring框架提供了模拟对象和测试支持类,本章将对此进行描述。
2.1Mock Objects(模拟对象)
Spring包含许多用于模拟的包:
Environment
JNDI
Servlet API
Spring Web Reactive
2.1.1 Environment
org.springframework.mock.env
包包含Environment
和PropertySource抽象的模拟实现(参见 Bean Definition Profiles和PropertySource
Abstraction)。MockEnvironment和MockPropertySource对于为依赖于环境特定属性的代码开发容器外测试非常有用。
2.1.2 JNDI
org.springframework.mock.jndi包包含jndi SPI的部分实现,您可以使用它为测试套件或独立应用程序设置简单的jndi环境。例如,如果JDBC数据源实例在测试代码中与在Java EE容器中绑定到相同的JNDI名称,则可以在测试场景中重用应用程序代码和配置,而无需修改。
警告:org.springframework.mock.jndi包中的模拟JNDI支持在Spring Framework 5.2开始正式弃用,转而支持来自第三方(如Simple-JNDI)的完整解决方案。
2.1.3 Servlet API
org.springframework.mock.web包包含一组全面的Servlet API模拟对象,它们对于测试web上下文、控制器和过滤器非常有用。这些模拟对象是针对Spring的Web MVC框架使用的,通常比动态模拟对象(如 EasyMock)或其他Servlet API模拟对象(如 MockObjects)使用起来更方便。
注意:从Spring Framework 5.0开始,org.springframework.mock.web中的模拟对象是基于Servlet 4.0 API的。
Spring MVC测试框架构建在模拟Servlet API对象上,为Spring MVC提供集成测试框架。参见Spring MVC测试框架 Spring MVC Test Framework。
2.1.4 Spring Web Reactive(Spring Web活性)
org.springframework.mock.http.server.reactive包包含ServerHttpRequest和ServerHttpResponse的模拟实现,用于WebFlux应用程序。org.springframework.mock.web.server包包含一个依赖于这些模拟请求和响应对象的模拟ServerWebExchange。
MockServerHttpRequest和MockServerHttpResponse都是从与服务器特定的实现相同的抽象基类扩展而来,并与它们共享行为。例如,模拟请求创建后是不可变的,但是您可以使用ServerHttpRequest中的mutate()方法来创建修改后的实例。
为了让模拟响应正确地实现写契约并返回写完成句柄(即,Mono<Void>),它在默认情况下使用cache().then()的通量,它缓冲数据并使其可用于测试中的断言。应用程序可以设置自定义写函数(例如,测试无限流)。、
WebTestClient构建在模拟请求和响应的基础上,为测试没有HTTP服务器的WebFlux应用程序提供支持。客户端还可以用于运行服务器的端到端测试。
2.2 单元测试支持类
Spring包含了许多可以帮助进行单元测试的类。它们分为两类:
2.2.1 一般的测试工具
org.springframework.test.util包包含几个用于单元测试和集成测试的通用实用程序。
ReflectionTestUtils是一组基于反射的实用方法。您可以在测试场景中使用这些方法,在这些场景中,您需要更改常量的值、设置非公共字段、调用非公共setter方法,或者在测试应用程序代码时调用非公共配置或生命周期回调方法,例如:
- ORM框架(如JPA和Hibernate)允许私有或受保护的字段访问,而不是域实体中的属性的公共setter方法。
- Spring对注释(如@Autowired、@Inject和@Resource)的支持,为私有或受保护的字段、setter方法和配置方法提供依赖注入。
- 在生命周期回调方法中使用@PostConstruct和@PreDestroy等注释。
AopTestUtils是一组与aop相关的实用方法。您可以使用这些方法来获取隐藏在一个或多个Spring代理背后的底层目标对象的引用。例如,如果您使用EasyMock或Mockito之类的库将bean配置为动态模拟,并且将模拟包装在Spring代理中,那么您可能需要直接访问底层模拟,以配置对它的期望并执行验证。有关Spring的核心AOP实用程序,请参见AopUtils和AopProxyUtils。
2.2.2 Spring MVC测试工具
org.springframework.test.web包包含ModelAndViewAssert,您可以将其与JUnit、TestNG或任何其他用于处理Spring MVC ModelAndView对象的单元测试的测试框架组合使用。
注意:单元测试Spring MVC控制器
要将Spring MVC控制器类作为pojo进行单元测试,可以从Spring的Servlet API模拟中使用ModelAndViewAssert与MockHttpServletRequest、MockHttpSession等进行组合。要将Spring MVC和REST控制器类与Spring MVC的WebApplicationContext配置结合起来进行全面的集成测试,请使用Spring MVC测试框架。
3. 集成测试
本节(本章其余大部分内容)将介绍Spring应用程序的集成测试。它包括下列主题:
Overview
Goals of Integration Testing
JDBC Testing Support
Annotations
Spring TestContext Framework
Spring MVC Test Framework
3.1 概述
在不需要部署到应用服务器或连接到其他企业基础设施的情况下,能够执行一些集成测试是很重要的。这样做可以让您测试以下内容:
- Spring IoC容器上下文的正确连接。
- 使用JDBC或ORM工具进行数据访问。这可能包括SQL语句、Hibernate查询、JPA实体映射等的正确性。
Spring框架为Spring -test模块中的集成测试提供了一流的支持。实际JAR文件的名称可能包含发布版本,也可能在长org.springframework中。测试表单,这取决于您从哪里获得它(有关依赖项管理的解释,请参阅相关章节)。这个库包括org.springframework。测试包,其中包含有价值的类,用于与Spring容器进行集成测试。此测试不依赖于应用服务器或其他部署环境。此类测试的运行速度比单元测试慢,但比等效的Selenium测试或依赖于部署到应用服务器的远程测试快得多。
单元和集成测试支持以注解驱动的 Spring TestContext Frameworkt的形式提供。TestContext框架不知道实际使用的测试框架,它允许在各种环境中测试的插装,包括JUnit、TestNG和其他环境。
3.2 集成测试的目标
Spring的集成测试支持有以下主要目标:
- 在测试之间管理Spring IoC容器缓存。
- 提供测试装置实例的依赖项注入。
- 提供适合集成测试的事务管理。
- 提供特定于spring的基类,以帮助开发人员编写集成测试。
接下来的几节描述每个目标,并提供实现和配置细节的链接。
3.2.1 上下文管理和缓存
Spring TestContext框架提供了Spring ApplicationContext实例和WebApplicationContext实例的一致加载,以及这些上下文的缓存。支持已加载上下文的缓存非常重要,因为启动时间可能成为一个问题—不是因为Spring本身的开销,而是因为Spring容器实例化的对象需要一定的时间来实例化。例如,一个有50到100个Hibernate映射文件的项目可能需要10到20秒来加载映射文件,在每个测试装置中运行每个测试之前的成本会导致整体测试运行变慢,从而降低开发人员的工作效率。
测试类通常声明XML或Groovy配置元数据的资源位置数组(通常在类路径中),或用于配置应用程序的组件类数组。这些位置或类与用于生产部署的web.xml或其他配置文件中指定的位置或类相同或类似。
默认情况下,一旦加载,配置的ApplicationContext将被每个测试重用。因此,每个测试套件只产生一次设置成本,并且后续的测试执行要快得多。在此上下文中,术语“测试套件”意味着所有测试都在相同的JVM中运行——例如,所有测试都是从Ant、Maven或Gradle构建中为给定的项目或模块运行的。在测试破坏应用程序上下文并需要重新加载(例如,通过修改bean定义或应用程序对象的状态)的不太可能的情况下,可以将TestContext框架配置为在执行下一个测试之前重新加载配置并重新构建应用程序上下文。
参见TestContext框架的 Context Management和 Context Caching。
3.2.2 Dependency Injection of Test Fixtures
当TestContext框架加载您的应用程序上下文时,它可以选择使用依赖注入来配置您的测试类实例。这为使用来自应用程序上下文的预配置bean来设置测试装置提供了一种方便的机制。这样做的一个很大的好处是,您可以跨各种测试场景重用应用程序上下文(例如,用于配置spring管理的对象图、事务代理、数据源实例等),从而避免为单个测试用例重复复杂的测试夹具设置。
例如,考虑这样一个场景:我们有一个类(HibernateTitleRepository),它为一个标题域实体实现了数据访问逻辑。我们想写集成测试,测试以下领域:
- Spring配置:基本上,所有与HibernateTitleRepository bean的配置相关的内容都正确且正确吗?
- Hibernate映射文件配置:所有内容都正确映射了吗?正确的延迟加载设置就位了吗?
- HibernateTitleRepository的逻辑:该类的配置实例是否按预期执行?
请参阅 TestContext framework中测试装置的依赖项注入。
3.2.3 事务管理
在访问实际数据库的测试中,一个常见的问题是它们对持久性存储的状态的影响。即使在使用开发数据库时,对状态的更改也可能影响未来的测试。此外,许多操作(例如插入或修改持久数据)不能在事务外部执行(或验证)。
TestContext框架解决了这个问题。默认情况下,框架为每个测试创建并回滚一个事务。您可以编写假定存在事务的代码。如果在测试中调用事务代理对象,它们将根据配置的事务语义正确地运行。此外,如果测试方法在为测试而管理的事务中运行时删除所选表的内容,则事务默认回滚,数据库返回到执行测试之前的状态。通过使用在测试的应用程序上下文中定义的PlatformTransactionManager bean,可以为测试提供事务支持。
如果您希望提交一个事务(不常见,但是在您希望填充或修改数据库的特定测试时偶尔有用),您可以告诉TestContext框架使事务提交,而不是使用@Commit注释进行回滚。
3.2.4 支持集成测试类
Spring TestContext框架提供了几个抽象的支持类,它们简化了集成测试的编写。这些基本测试类提供了良好定义的测试框架挂钩,以及方便的实例变量和方法,让您访问:
- ApplicationContext,用于执行显式bean查找或测试上下文的整体状态。
- 用于执行SQL语句来查询数据库的JdbcTemplate。您可以在执行与数据库相关的应用程序代码之前和之后使用这些查询来确认数据库状态,Spring确保这些查询在与应用程序代码相同的事务范围内运行。当与ORM工具一起使用时,一定要避免误报。
此外,您可能希望使用特定于项目的实例变量和方法创建自己的自定义应用程序级超类。
参见 TestContext framework的支持类。
3.3 JDBC Testing Support
org.springframework.test.jdbc包包含JdbcTestUtils,这是一组与jdbc相关的实用函数,旨在简化标准的数据库测试场景。具体来说,JdbcTestUtils提供了以下静态实用程序方法。
- countRowsInTable(..):计算给定表中的行数。
- countRowsInTableWhere(..):使用提供的WHERE子句计算给定表中的行数。
- deleteFromTables(..):删除指定表中的所有行。
- deleteFromTableWhere(..):使用提供的WHERE子句从给定表中删除行。
- dropTables(..):删除指定的表。
注意:AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests提供了方便的方法,可以在JdbcTestUtils中委托上述方法。
spring-jdbc模块提供了对配置和启动嵌入式数据库的支持,您可以在与数据库交互的集成测试中使用它。有关详细信息,请参阅嵌入式数据库支持 Embedded Database Support和使用嵌入式数据库测试数据访问逻辑Testing Data Access Logic with an Embedded Database。
3.4 注释
3.4.1 Spring 测试注释
Spring框架提供了以下一组特定于Spring的注释,您可以将它们与TestContext框架一起用于单元和集成测试中。有关更多信息,请参见相应的javadoc,包括默认属性值、属性别名和其他详细信息。
Spring的测试注释包括以下内容:
@BootstrapWith
@ContextConfiguration
@WebAppConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DirtiesContext
@TestExecutionListeners
@Commit
@Rollback
@BeforeTransaction
@AfterTransaction
@Sql
@SqlConfig
@SqlMergeMode
@SqlGroup
@BootstrapWith
@BootstrapWith是一个类级注释,您可以使用它来配置如何引导Spring TestContext框架。具体来说,您可以使用@BootstrapWith来指定自定义TestContextBootstrapper。有关更多详细信息,请参见启动TestContext框架一节。
@ContextConfiguration
@ContextConfiguration定义类级元数据,用于确定如何为集成测试加载和配置ApplicationContext。具体来说,@ContextConfiguration声明应用程序上下文资源位置或用于加载上下文的组件类。
资源位置通常是位于类路径中的XML配置文件或Groovy脚本,而组件类通常是@Configuration类。但是,资源位置也可以引用文件系统中的文件和脚本,组件类可以是@Component类、@Service类等等。有关详细信息,请参见组件类Component Classes。
下面的示例显示了引用XML文件的@ContextConfiguration注释:
@ContextConfiguration("/test-config.xml")
class XmlApplicationContextTests {// class body...
}
下面的示例显示了引用类的@ContextConfiguration注释:
@ContextConfiguration(classes = TestConfig.class)
class ConfigClassApplicationContextTests {// class body...
}
除了声明资源位置或组件类之外,还可以使用@ContextConfiguration声明ApplicationContextInitializer类。下面是一个例子:
@ContextConfiguration(initializers = CustomContextIntializer.class)
class ContextInitializerTests {// class body...
}
您也可以选择使用@ContextConfiguration来声明ContextLoader策略。但是,请注意,通常不需要显式地配置加载程序,因为默认加载程序支持初始化器和资源位置或组件类。
下面的例子同时使用了位置和加载程序:
@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class)
class CustomLoaderXmlApplicationContextTests {// class body...
}
注意:@ContextConfiguration支持继承资源位置或配置类以及由超类声明的上下文初始化器。
有关更多细节,请参见 Context Management和@ContextConfiguration javadocs。
@WebAppConfiguration
@WebAppConfiguration是一个类级注释,您可以使用它来声明为集成测试加载的ApplicationContext应该是WebApplicationContext。仅在测试类上存在@WebAppConfiguration,就可以确保使用“file:src/main/webapp”作为web应用程序根路径(即资源基路径)的默认值,为测试加载WebApplicationContext。资源基路径在后台用于创建MockServletContext,它作为测试的WebApplicationContext的ServletContext。
下面的示例展示了如何使用@WebAppConfiguration注释:
@ContextConfiguration
@WebAppConfiguration
class WebAppTests {// class body...
}
要覆盖默认值,可以使用隐式值属性指定不同的基本资源路径。同时支持classpath:和file: resource前缀。如果没有提供资源前缀,则假定路径是文件系统资源。下面的例子展示了如何指定类路径资源:
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources")
class WebAppTests {// class body...
}
注意,@WebAppConfiguration必须与@ContextConfiguration结合使用,可以在单个测试类中使用,也可以在测试类层次结构中使用。更多细节请参见@WebAppConfiguration
javadoc。
@ContextHierarchy
@ context是一个类级注释,用于定义集成测试的ApplicationContext实例的层次结构。@ContextConfiguration应该使用一个或多个@ContextConfiguration实例的列表来声明,每个实例在上下文层次结构中定义一个级别。下面的例子演示了在单个测试类中使用@ context thier阿其(@ context thier阿其也可以在测试类层次结构中使用):
@ContextHierarchy({@ContextConfiguration("/parent-config.xml"),@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {// class body...
}
@WebAppConfiguration
@ContextHierarchy({@ContextConfiguration(classes = AppConfig.class),@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {// class body...
}
如果您需要在测试类层次结构中合并或覆盖上下文层次结构的给定级别的配置,您必须通过在类层次结构中的每个对应级别上为@ContextConfiguration中的name属性提供相同的值来显式地命名该级别。有关更多示例,请参见上下文层次结构和@Context thierarchjavadoc。
@ActiveProfiles
@ActiveProfiles是一个类级别的注释,用于在为集成测试加载ApplicationContext时声明哪个bean定义配置文件应该是活动的。
下面的例子说明dev配置文件应该是活动的:
@ContextConfiguration
@ActiveProfiles("dev")
class DeveloperTests {// class body...
}
下面的例子表明,开发和集成配置文件都应该是活动的:
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
class DeveloperIntegrationTests {// class body...
}
指示开发和集成概要文件应该是活动的。
注意:@ActiveProfiles支持继承由超类默认声明的活动bean定义配置文件。还可以通过实现自定义ActiveProfilesResolver并使用@ActiveProfiles的resolver属性注册它,以编程方式解析活动bean定义概要。
@TestPropertySource
@TestPropertySource是一个类级别的注释,您可以使用它来配置属性文件的位置和内联属性,这些属性将被添加到环境中的PropertySources集合中,以便为集成测试加载ApplicationContext。
与从操作系统环境或Java系统属性加载的属性源以及通过@PropertySource或编程方式由应用程序添加的属性源相比,测试属性源具有更高的优先级。因此,可以使用测试属性源选择性地覆盖系统和应用程序属性源中定义的属性。此外,内联属性具有比从资源位置加载的属性更高的优先级。
下面的例子演示了如何从类路径声明属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties")
class MyIntegrationTests {// class body...
}
下面的例子演示了如何声明内联属性:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
class MyIntegrationTests {// class body...
}
@DirtiesContext
@DirtiesContext表示在测试执行过程中底层的Spring ApplicationContext已经被污染(也就是说,测试以某种方式修改或破坏了它——例如,通过改变一个单例bean的状态),应该被关闭。当应用程序上下文被标记为dirty时,它将从测试框架的缓存中删除并关闭。因此,对于需要具有相同配置元数据的上下文的任何后续测试,都要重新构建底层Spring容器。
您可以使用@DirtiesContext作为同一个类或类层次结构中的类级和方法级注释。在这样的场景中,根据配置的methodMode和classMode, ApplicationContext在任何这样的注释方法之前或之后,以及在当前测试类之前或之后被标记为dirty。
下面的例子解释了各种配置场景的上下文何时会被弄脏:
- 在当前测试类之前,在类模式设置为BEFORE_CLASS的类上声明。
@DirtiesContext(classMode = BEFORE_CLASS)
class FreshContextTests {// some tests that require a new Spring container
}
- 在当前测试类之后,在类模式设置为AFTER_CLASS(即,默认的类模式)。
@DirtiesContext
class ContextDirtyingTests {// some tests that result in the Spring container being dirtied
}
- 在当前测试类中的每个测试方法之前,在类上声明时,将类模式设置为BEFORE_EACH_TEST_METHOD。
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
class FreshContextTests {// some tests that require a new Spring container
}
- 在当前测试类中的每个测试方法之后,在类上声明,并将类模式设置为AFTER_EACH_TEST_METHOD。
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
class ContextDirtyingTests {// some tests that result in the Spring container being dirtied
}
- 在当前测试之前,在方法上声明时,方法模式设置为BEFORE_METHOD。
@DirtiesContext(methodMode = BEFORE_METHOD)
@Test
void testProcessWhichRequiresFreshAppCtx() {// some logic that requires a new Spring container
}
- 在当前测试之后,当方法模式设置为AFTER_METHOD(即,默认方法模式)。
@DirtiesContext
@Test
void testProcessWhichDirtiesAppCtx() {// some logic that results in the Spring container being dirtied
}
如果您在一个测试中使用@DirtiesContext,而该测试的上下文被配置为带有@ context层次结构的上下文层次结构的一部分,那么您可以使用hierarchyMode标志来控制上下文缓存被清除的方式。默认情况下,使用穷举算法来清除上下文缓存,不仅包括当前级别,还包括所有其他上下文层次结构,它们共享当前测试的公共祖先上下文。所有驻留在公共祖先上下文的子层次结构中的ApplicationContext实例将从上下文缓存中删除并关闭。如果穷举算法对于特定的用例来说是多余的,您可以指定更简单的当前级别算法,如下面的示例所示。
@ContextHierarchy({@ContextConfiguration("/parent-config.xml"),@ContextConfiguration("/child-config.xml")
})
class BaseTests {// class body...
}class ExtendedTests extends BaseTests {@Test@DirtiesContext(hierarchyMode = CURRENT_LEVEL) void test() {// some logic that results in the child context being dirtied}
}
有关穷举级和当前级算法的详细信息,请参阅DirtiesContext.HierarchyMode
javadoc。
@TestExecutionListeners
@TestExecutionListener为配置TestExecutionListener实现定义类级元数据,这些实现应该注册到TestContextManager。通常,@ testexecutionlistener与@ContextConfiguration一起使用。
下面的例子展示了如何注册两个TestExecutionListener实现:
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class})
class CustomTestExecutionListenerTests {// class body...
}
默认情况下,@ testexecutionlistener支持继承的侦听器。有关示例和更多细节,请参见javadoc。
@Commit
@Commit表示事务测试方法的事务应该在测试方法完成后提交。您可以使用@Commit作为@Rollback(false)的直接替代,以便更明确地传达代码的意图。与@Rollback类似,@Commit也可以声明为类级或方法级注释。
下面的例子演示了如何使用@Commit注释:
@Commit
@Test
void testProcessWithoutRollback() {// ...
}
@Rollback
@Rollback指示在测试方法完成后,是否应该回滚事务性测试方法的事务。如果为真,则回滚事务。否则,事务将被提交(参见@Commit)。即使@Rollback没有显式声明,Spring TestContext框架中的集成测试的回滚也默认为true。
当声明为类级注释时,@Rollback为测试类层次结构中的所有测试方法定义默认的回滚语义。当声明为方法级注释时,@Rollback为特定的测试方法定义回滚语义,可能会覆盖类级@Rollback或@Commit语义。
下面的示例导致测试方法的结果不能回滚(即,结果被提交到数据库):
@Rollback(false)
@Test
void testProcessWithoutRollback() {// ...
}
@BeforeTransaction
@BeforeTransaction指出,在事务启动之前应该运行带注释的void方法,因为使用Spring的@Transactional注释已经配置为在事务中运行的测试方法。@BeforeTransaction方法不需要是公共的,可以在基于Java 8的接口默认方法上声明。
下面的示例展示了如何使用@BeforeTransaction注释:
@BeforeTransaction
void beforeTransaction() {// logic to be executed before a transaction is started
}
@AfterTransaction
@AfterTransaction表示,在事务结束后应该运行带注释的void方法,因为已经使用Spring的@Transactional注释将测试方法配置为在事务中运行。@AfterTransaction方法不需要是公共的,可以在基于Java 8的接口默认方法上声明。
@AfterTransaction
@AfterTransaction表示,在事务结束后应该运行带注释的void方法,因为已经使用Spring的@Transactional注释将测试方法配置为在事务中运行。@AfterTransaction方法不需要是公共的,可以在基于Java 8的接口默认方法上声明。
在事务之后运行此方法。
@Sql
@Sql用于注释一个测试类或测试方法,以配置在集成测试期间针对给定数据库运行的SQL脚本。下面的例子展示了如何使用它:
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
void userTest() {// execute code that relies on the test schema and test data
}
@SqlConfig
@SqlConfig定义用于确定如何解析和运行配置了@Sql注释的SQL脚本的元数据。下面的例子展示了如何使用它:
@Test
@Sql(scripts = "/test-user-data.sql",config = @SqlConfig(commentPrefix = "`", separator = "@@")
)
void userTest() {// execute code that relies on the test data
}
@SqlMergeMode
@SqlMergeMode用于注释一个测试类或测试方法,以配置方法级别的@Sql声明是否与类级别的@Sql声明合并。如果@SqlMergeMode没有在测试类或测试方法上声明,则默认情况下将使用覆盖合并模式。使用覆盖模式,方法级@Sql声明将有效地覆盖类级@Sql声明。
注意,方法级@SqlMergeMode声明覆盖了类级声明。
下面的示例展示了如何在类级别上使用@SqlMergeMode。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE)
class UserTests {@Test@Sql("/user-test-data-001.sql")void standardUserProfile() {// execute code that relies on test data set 001}
}
下面的示例展示了如何在方法级别上使用@SqlMergeMode。
@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {@Test@Sql("/user-test-data-001.sql")@SqlMergeMode(MERGE) void standardUserProfile() {// execute code that relies on test data set 001}
}
@SqlGroup
@SqlGroup是一个聚合多个@Sql注释的容器注释。您可以直接使用@SqlGroup来声明几个嵌套的@Sql注释,也可以将它与Java 8对可重复注释的支持一起使用,其中可以在同一个类或方法上多次声明@Sql,从而隐式地生成这个容器注释。下面的例子展示了如何声明一个SQL组:
@Test
@SqlGroup({ @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),@Sql("/test-user-data.sql")
)}
void userTest() {// execute code that uses the test schema and test data
}
3.4.2 标准注释支持
对于所有配置的Spring TestContext框架,以下注释都支持标准的语义。注意,这些注释不是特定于测试的,可以在Spring框架的任何地方使用。
@Autowired
@Qualifier
@Value
@Resource
(javax.annotation) if JSR-250 is present@ManagedBean
(javax.annotation) if JSR-250 is present@Inject
(javax.inject) if JSR-330 is present@Named
(javax.inject) if JSR-330 is present@PersistenceContext
(javax.persistence) if JPA is present@PersistenceUnit
(javax.persistence) if JPA is present@Required
@Transactional
(org.springframework.transaction.annotation) with limited attribute support
注意:JSR-250生命周期注释
在Spring TestContext框架中,您可以在ApplicationContext中配置的任何应用程序组件上使用带有标准语义的@PostConstruct和@PreDestroy。然而,这些生命周期注释在实际的测试类中使用有限。
如果测试类中的方法使用@PostConstruct进行了注释,那么该方法将在底层测试框架的任何before方法之前运行(例如,使用JUnit Jupiter的@BeforeEach进行注释的方法),这适用于测试类中的每个测试方法。另一方面,如果测试类中的方法被@PreDestroy注释,那么这个方法就永远不会运行。因此,在测试类中,我们建议您使用来自底层测试框架的测试生命周期回调,而不是使用@PostConstruct和@PreDestroy。
3.4.3 Spring JUnit 4测试注释
以下注释仅在与SpringRunner、Spring的JUnit 4规则或Spring的JUnit 4支持类一起使用时才受支持:
@IfProfileValue
@ProfileValueSourceConfiguration
@Timed
@Repeat
@IfProfileValue
@IfProfileValue指示为特定的测试环境启用了带注释的测试。如果配置的ProfileValueSource返回与提供的名称匹配的值,则启用测试。否则,测试将被禁用并有效地忽略。
您可以在类级、方法级或同时应用@IfProfileValue。对于该类或其子类中的任何方法,@IfProfileValue的类级用法优先于方法级用法。具体地说,如果在类级和方法级都启用了测试,那么测试就是启用的。没有@IfProfileValue意味着测试是隐式启用的。这类似于JUnit 4的@Ignore注释的语义,只是@Ignore的存在总是禁用测试。
下面的例子展示了一个带有@IfProfileValue注释的测试:
@IfProfileValue(name="java.vendor", value="Oracle Corporation")
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {// some logic that should run only on Java VMs from Oracle Corporation
}
或者,您可以配置@IfProfileValue一个值列表(带有或语义),以在JUnit 4环境中实现对测试组的类似testng的支持。考虑下面的例子:
@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"})
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {// some logic that should run only for unit and integration test groups
}
@ProfileValueSourceConfiguration
@ProfileValueSourceConfiguration是一个类级注释,它指定在检索通过@IfProfileValue注释配置的配置文件值时使用什么类型的ProfileValueSource。如果没有为测试声明@ProfileValueSourceConfiguration,则默认使用SystemProfileValueSource。下面的例子演示了如何使用@ProfileValueSourceConfiguration:
@ProfileValueSourceConfiguration(CustomProfileValueSource.class)
public class CustomProfileValueSourceTests {// class body...
}
@Timed
@ timing表示带注释的测试方法必须在指定的时间段内(以毫秒为单位)完成执行。如果文本执行时间超过指定的时间段,则测试失败。
这段时间包括运行测试方法本身,测试的任何重复(参见@Repeat),以及测试夹具的任何设置或拆卸。下面的例子展示了如何使用它:
@Timed(millis = 1000)
public void testProcessWithOneSecondTimeout() {// some logic that should not take longer than 1 second to execute
}
Spring的@ timing注释与JUnit 4的@Test(timeout=…)支持具有不同的语义。具体来说,由于JUnit 4处理测试执行超时的方式(也就是说,通过在单独的线程中执行测试方法),如果测试花费的时间太长,@Test(timeout=…)会预先导致测试失败。另一方面,Spring的@ timing不会先发制人地使测试失败,而是在失败之前等待测试完成。
@Repeat
@Repeat指示必须重复运行带注释的测试方法。要执行的测试方法的次数在注释中指定。
重复执行的范围包括测试方法本身的执行,以及测试夹具的任何设置或拆卸。下面的例子演示了如何使用@Repeat注释:
@Repeat(10)
@Test
public void testProcessRepeatedly() {// ...
}
3.4.4 Spring JUnit测试注释
以下注释仅在与SpringExtension和JUnit Jupiter(即JUnit 5中的编程模型)一起使用时才受支持:
@SpringJUnitConfig
@SpringJUnitWebConfig
@TestConstructor
@EnabledIf
@DisabledIf
@SpringJUnitConfig
@SpringJUnitConfig是一个组合了来自JUnit Jupiter的@ExtendWith(SpringExtension.class)和来自Spring TestContext框架的@ContextConfiguration的注释。它可以在类级别上作为@ContextConfiguration的替代。关于配置选项,@ContextConfiguration和@SpringJUnitConfig之间的唯一区别是组件类可以用@SpringJUnitConfig中的value属性声明。
下面的示例演示了如何使用@SpringJUnitConfig注释来指定配置类:
@SpringJUnitConfig(TestConfig.class)
class ConfigurationClassJUnitJupiterSpringTests {// class body...
}
下面的示例展示了如何使用@SpringJUnitConfig注释来指定配置文件的位置:
@SpringJUnitConfig(locations = "/test-config.xml")
class XmlJUnitJupiterSpringTests {// class body...
}
有关更多细节,请参见上下文管理以及@SpringJUnitConfig和@ContextConfiguration的javadoc。
@SpringJUnitWebConfig
@SpringJUnitWebConfig是一个组合了来自JUnit Jupiter的@ExtendWith(SpringExtension.class)与来自Spring TestContext框架的@ContextConfiguration和@WebAppConfiguration的注释。您可以在类级别使用它作为@ContextConfiguration和@WebAppConfiguration的替代。关于配置选项,@ContextConfiguration和@SpringJUnitWebConfig之间的惟一区别是您可以通过使用@SpringJUnitWebConfig中的值属性来声明组件类。另外,您只能通过使用@SpringJUnitWebConfig中的resourcePath属性来覆盖来自@WebAppConfiguration的值属性。
下面的例子演示了如何使用@SpringJUnitWebConfig注释来指定配置类:
@SpringJUnitWebConfig(TestConfig.class)
class ConfigurationClassJUnitJupiterSpringWebTests {// class body...
}
下面的例子演示了如何使用@SpringJUnitWebConfig注释来指定配置文件的位置:
@SpringJUnitWebConfig(locations = "/test-config.xml")
class XmlJUnitJupiterSpringWebTests {// class body...
}
有关更多细节,请参见@SpringJUnitWebConfig、@ContextConfiguration和@WebAppConfiguration的上下文管理和javadoc。
@TestConstructor
@TestConstructor是一个类型级别的注释,用于配置如何从测试的ApplicationContext中的组件自动获取测试类构造函数的参数。
如果@TestConstructor在测试类上不存在或元存在,则将使用默认的测试构造函数自动连接模式。有关如何更改默认模式的详细信息,请参阅下面的提示。但是请注意,构造函数上的@Autowired局部声明优先于@TestConstructor和默认模式。
注意:更改默认的测试构造函数自动连接模式
可以通过设置spring.test.constructor.autowire来更改默认的测试构造函数autowire模式。将JVM系统属性模式设置为all。另外,可以通过SpringProperties机制更改默认模式。
如果spring.test.constructor.autowire.mode属性未设置,测试类构造函数将不会自动自动生成。
从Spring Framework 5.2开始,@TestConstructor只支持与SpringExtension一起使用,以与JUnit Jupiter一起使用。注意,SpringExtension通常会自动为您注册——例如,当使用诸如@SpringJUnitConfig和@SpringJUnitWebConfig等注释,或者使用来自Spring引导测试的各种与测试相关的注释时。
@EnabledIf
@EnabledIf用于表示已启用注释的JUnit Jupiter测试类或测试方法,如果提供的表达式的计算结果为true,则应运行该方法。具体来说,如果表达式计算为布尔值。TRUE或一个等于TRUE的字符串(忽略大小写),测试是启用的。在类级应用时,默认情况下也会自动启用该类中的所有测试方法。
表达方式可以是以下任何一种:
- Spring表达式语言(SpEL)表达式。例如:@EnabledIf (" # {systemProperties [' os.name '] .toLowerCase () .contains (mac)}”)
- Spring环境中可用属性的占位符。例如:@EnabledIf (" $ {smoke.tests.enabled} ")
- 文本文字。例如:@EnabledIf(“true”)
但是,请注意,不是属性占位符的动态解析结果的文本文本的实际价值为零,因为@EnabledIf(“false”)与@Disabled和@EnabledIf(“true”)在逻辑上是没有意义的。
您可以使用@EnabledIf作为元注释来创建自定义复合注释。例如,您可以创建一个自定义的@EnabledOnMac注释,如下所示:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@EnabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",reason = "Enabled on Mac OS"
)
public @interface EnabledOnMac {}
@DisabledIf
@DisabledIf用于表示已注释的JUnit Jupiter测试类或测试方法已禁用,如果提供的表达式计算结果为true,则不应执行。具体来说,如果表达式计算为布尔值。如果一个字符串等于TRUE(忽略大小写),则禁用该测试。当应用于类级别时,该类中的所有测试方法也会自动禁用。
表达方式可以是以下任何一种:
- Spring表达式语言(SpEL)表达式。例如:@DisabledIf (" # {systemProperties [' os.name '] .toLowerCase () .contains (mac)}”)
- Spring环境中可用属性的占位符。例如:@DisabledIf (" $ {smoke.tests.disabled} ")
- 文本文字。例如:@DisabledIf(“true”)
但是,请注意,不是属性占位符的动态解析结果的文本文本的实际价值为零,因为@DisabledIf(“true”)与@Disabled和@DisabledIf(“false”)在逻辑上是没有意义的。
您可以使用@DisabledIf作为元注释来创建自定义复合注释。例如,您可以创建一个自定义的@DisabledOnMac注释,如下所示:
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@DisabledIf(expression = "#{systemProperties['os.name'].toLowerCase().contains('mac')}",reason = "Disabled on Mac OS"
)
public @interface DisabledOnMac {}
3.4.5 对测试的元注释支持
您可以使用大多数与测试相关的注释作为元注释来创建自定义复合注释,并减少跨测试套件的配置重复。
您可以使用下面的每一个作为与TestContext框架相结合的元注释。
@BootstrapWith
@ContextConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DirtiesContext
@WebAppConfiguration
@TestExecutionListeners
@Transactional
@BeforeTransaction
@AfterTransaction
@Commit
@Rollback
@Sql
@SqlConfig
@SqlMergeMode
@SqlGroup
@Repeat
(only supported on JUnit 4)@Timed
(only supported on JUnit 4)@IfProfileValue
(only supported on JUnit 4)@ProfileValueSourceConfiguration
(only supported on JUnit 4)@SpringJUnitConfig
(only supported on JUnit Jupiter)@SpringJUnitWebConfig
(only supported on JUnit Jupiter)@TestConstructor
(only supported on JUnit Jupiter)@EnabledIf
(only supported on JUnit Jupiter)@DisabledIf
(only supported on JUnit Jupiter)
举例:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class OrderRepositoryTests { }@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public class UserRepositoryTests { }
如果我们发现我们在基于JUnit 4的测试套件中重复了前面的配置,我们可以通过引入自定义复合注释来减少重复,该注释集中了Spring的公共测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后我们可以使用自定义的@TransactionalDevTestConfig注释来简化单个基于JUnit 4的测试类的配置,如下所示:
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
如果我们编写使用JUnit Jupiter的测试,我们可以进一步减少代码重复,因为JUnit 5中的注释也可以用作元注释。考虑下面的例子:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class OrderRepositoryTests { }@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
class UserRepositoryTests { }
如果我们发现我们在基于JUnit木星的测试套件中重复前面的配置,我们可以通过引入自定义组合注释来减少重复,该注释集中了Spring和JUnit木星的公共测试配置,如下所示:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后我们可以使用自定义的@TransactionalDevTestConfig注释来简化基于JUnit Jupiter的单个测试类的配置,如下所示:
@TransactionalDevTestConfig
class OrderRepositoryTests { }@TransactionalDevTestConfig
class UserRepositoryTests { }
由于JUnit Jupiter支持使用@Test、@RepeatedTest、ParameterizedTest等作为元注释,所以您还可以在测试方法级别创建自定义复合注释。例如,如果我们希望创建一个组合注释,将来自JUnit Jupiter的@Test和@Tag注释与来自Spring的@Transactional注释结合起来,我们可以创建一个@TransactionalIntegrationTest注释,如下所示:
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Transactional
@Tag("integration-test") // org.junit.jupiter.api.Tag
@Test // org.junit.jupiter.api.Test
public @interface TransactionalIntegrationTest { }
然后我们可以使用自定义的@TransactionalIntegrationTest注释来简化基于JUnit Jupiter的测试方法的配置,如下所示:
@TransactionalIntegrationTest
void saveOrder() { }@TransactionalIntegrationTest
void deleteOrder() { }
有关详细信息,请参见Spring注释编程模型wiki页面 Spring Annotation Programming Model。
3.5 Spring和TestContext框架
Spring TestContext框架(位于org.springframework.test.context包)提供了通用的、注释驱动的单元和集成测试支持,这与所使用的测试框架无关。TestContext框架还非常重视约定而不是配置,您可以通过基于注释的配置来覆盖这些合理的默认设置。
除了通用的测试基础设施之外,TestContext框架还提供了对JUnit 4、JUnit Jupiter(又名JUnit 5)和TestNG的显式支持。对于JUnit 4和TestNG, Spring提供了抽象的支持类。此外,Spring还为JUnit 4提供了自定义JUnit运行器和自定义JUnit规则,并为JUnit Jupiter提供了自定义扩展,允许您编写所谓的POJO测试类。POJO测试类不需要扩展特定的类层次结构,比如抽象支持类。
下一节将概述TestContext框架的内部结构。如果您只对使用框架感兴趣,而对使用您自己的自定义监听器或自定义加载器扩展框架不感兴趣,那么您可以直接访问配置(context management, dependency injection, transaction management)、support classes和annotation support部分。
3.5.1 Key抽象
框架的核心由TestContextManager类和TestContext、TestExecutionListener和SmartContextLoader接口组成。为每个测试类创建一个TestContextManager(例如,用于执行JUnit Jupiter中单个测试类中的所有测试方法)。反过来,TestContextManager管理一个包含当前测试上下文的TestContext。TestContextManager还会随着测试的进展更新TestContext的状态,并将其委托给TestExecutionListener实现,后者通过提供依赖项注入、管理事务等等来检测实际的测试执行。SmartContextLoader负责为给定的测试类加载ApplicationContext。有关各种实现的更多信息和示例,请参见javadoc和Spring测试套件。
TestContext
TestContext封装了执行测试的上下文(不知道实际使用的测试框架),并为它负责的测试实例提供上下文管理和缓存支持。如果需要,TestContext也会委托给SmartContextLoader来加载ApplicationContext。
TestContextManager
TestContextManager是Spring TestContext框架的主要入口点,负责管理单个TestContext,并在定义良好的测试执行点向每个注册的TestExecutionListener发送事件信号:
- 在特定测试框架的任何“类之前”或“所有之前”方法之前。
- 后处理测试实例。
- 在特定测试框架的任何“之前”或“之前每个”方法之前。
- 在执行测试方法之前但在测试设置之后。
- 在测试方法执行之后,但在测试之前将其销毁。
- 在特定测试框架的任何“After”或“After each”方法之后。
- 在特定测试框架的任何“类后”或“毕竟”方法之后。
TestExecutionListener
TestExecutionListener定义了用于响应TestContextManager发布的测试执行事件的API, TestContextManager是注册侦听器的对象。看TestExecutionListener
Configuration。
上下文加载器
ContextLoader是一个策略接口,用于为Spring TestContext框架管理的集成测试加载ApplicationContext。您应该实现SmartContextLoader而不是这个接口来提供对组件类、活动bean定义概要文件、测试属性源、上下文层次结构和WebApplicationContext支持的支持。
SmartContextLoader是ContextLoader接口的扩展,它取代了原来最小的ContextLoader SPI。具体来说,SmartContextLoader可以选择处理资源位置、组件类或上下文初始化器。此外,SmartContextLoader可以设置活动bean定义配置文件,并在其加载的上下文中测试属性源。
Spring提供了以下实现:
- DelegatingSmartContextLoader:两个默认加载器之一,它代表内部AnnotationConfigContextLoader, GenericXmlContextLoader,或GenericGroovyXmlContextLoader,根据测试类声明的配置或在默认位置的存在或默认配置类。只有当Groovy位于类路径上时,才能启用Groovy支持。
- WebDelegatingSmartContextLoader:两个默认加载器之一,它代表内部AnnotationConfigWebContextLoader, GenericXmlWebContextLoader,或GenericGroovyXmlWebContextLoader,根据测试类声明的配置或在默认位置的存在或默认配置类。只有当@WebAppConfiguration出现在测试类中时才会使用web ContextLoader。只有当Groovy位于类路径上时,才能启用Groovy支持。
- 注释configcontextloader:从组件类装入标准的ApplicationContext。
- configwebcontextloader:从组件类加载WebApplicationContext。
- GenericGroovyXmlContextLoader:从资源位置(要么是Groovy脚本,要么是XML配置文件)加载标准的ApplicationContext。
- GenericGroovyXmlWebContextLoader:从资源位置(要么是Groovy脚本,要么是XML配置文件)加载WebApplicationContext。
- GenericXmlContextLoader:从XML资源位置加载标准的ApplicationContext。
- GenericXmlWebContextLoader:从XML资源位置加载WebApplicationContext。
- GenericPropertiesContextLoader:从Java属性文件中加载一个标准的ApplicationContext。
3.5.2 启动TestContext框架
Spring TestContext框架内部的默认配置对于所有的通用用例来说已经足够了。但是,有时开发团队或第三方框架希望更改默认的ContextLoader、实现自定义的TestContext或ContextCache、扩展默认的ContextCustomizerFactory集和TestExecutionListener实现集,等等。对于TestContext框架如何操作的这种低级控制,Spring提供了一种引导策略。
TestContextBootstrapper定义了用于引导TestContext框架的SPI。TestContextManager使用TestContextBootstrapper来加载当前测试的TestExecutionListener实现,并构建它所管理的TestContext。您可以使用@BootstrapWith直接或作为元注释为测试类(或测试类层次结构)配置自定义引导策略。如果没有使用@BootstrapWith显式配置bootstrapper,则使用DefaultTestContextBootstrapper或WebTestContextBootstrapper,具体取决于@WebAppConfiguration的存在。
因为TestContextBootstrapper SPI将来可能会改变(以适应新的需求),所以我们强烈建议实现者不要直接实现这个接口,而是扩展AbstractTestContextBootstrapper或它的一个具体子类。
3.5.3 TestExecutionListener配置
Spring提供了以下默认注册的TestExecutionListener实现,其顺序如下:
- ServletTestExecutionListener:为WebApplicationContext配置Servlet API模拟。
- DirtiesContextBeforeModesTestExecutionListener:处理“before”模式的@DirtiesContext注释。
- DependencyInjectionTestExecutionListener:为测试实例提供依赖注入。
- DirtiesContextTestExecutionListener:处理“后”模式的@DirtiesContext注释。
- TransactionalTestExecutionListener:提供具有默认回滚语义的事务性测试执行。
- SqlScriptsTestExecutionListener:运行使用@Sql注释配置的SQL脚本。
- EventPublishingTestExecutionListener:将测试执行事件发布到测试的ApplicationContext(参见测试执行事件)。
注册TestExecutionListener实现
可以使用@TestExecutionListener注释注册测试类及其子类的TestExecutionListener实现。有关详细信息和示例,请参见@ testexecutionlistener的注释支持和javadoc。
自动发现默认的TestExecutionListener实现
通过使用@TestExecutionListener注册TestExecutionListener实现适合于在有限的测试场景中使用的自定义监听器。但是,如果需要在整个测试套件中使用自定义侦听器,则会变得非常麻烦。这个问题是通过支持通过SpringFactoriesLoader机制自动发现默认TestExecutionListener实现来解决的。
具体来说,spring-test模块在org.springframework.test.context下声明了所有核心的默认TestExecutionListener实现。TestExecutionListener键的元inf /spring。工厂的属性文件。第三方框架和开发人员可以通过自己的META-INF/spring以相同的方式将自己的TestExecutionListener实现贡献给默认监听器列表。工厂的属性文件。
Ordering TestExecutionListener实现
当TestContext框架通过前面提到的SpringFactoriesLoader机制发现默认的TestExecutionListener实现时,通过使用Spring的AnnotationAwareOrderComparator对实例化的侦听器进行排序,该方法支持Spring的有序接口和用于排序的@Order注释。抽象TestExecutionListener和所有由Spring提供的默认TestExecutionListener实现,这些实现使用适当的值排序。因此,第三方框架和开发人员应该通过实现Ordered或声明@Order来确保他们的默认TestExecutionListener实现以正确的顺序注册。有关分配给每个核心侦听器的值的详细信息,请参阅核心默认TestExecutionListener实现的getOrder()方法的javadoc。
合并TestExecutionListener实现
如果自定义的TestExecutionListener是通过@TestExecutionListener注册的,则默认的侦听器不会注册。在大多数常见的测试场景中,这有效地迫使开发人员手动声明除任何自定义侦听器之外的所有默认侦听器。下面的清单演示了这种配置风格:
@ContextConfiguration
@TestExecutionListeners({MyCustomTestExecutionListener.class,ServletTestExecutionListener.class,DirtiesContextBeforeModesTestExecutionListener.class,DependencyInjectionTestExecutionListener.class,DirtiesContextTestExecutionListener.class,TransactionalTestExecutionListener.class,SqlScriptsTestExecutionListener.class
})
class MyTest {// class body...
}
此方法的挑战在于,它要求开发人员准确地知道默认情况下注册了哪些侦听器。此外,默认监听器的设置可以随着版本的不同而变化——例如,SqlScriptsTestExecutionListener是在Spring Framework 4.1中引入的,DirtiesContextBeforeModesTestExecutionListener是在Spring Framework 4.2中引入的。此外,像Spring Boot和Spring Security这样的第三方框架通过使用前面提到的自动发现机制来注册它们自己的默认TestExecutionListener实现。
为了避免必须意识到并重新声明所有默认监听器,可以将@ testexecutionlistener的mergeMode属性设置为mergeMode . merge_with_defaults。MERGE_WITH_DEFAULTS表示应该将本地声明的监听器与默认监听器合并。合并算法确保从列表中删除重复的侦听器,并根据AnnotationAwareOrderComparator的语义对合并后的侦听器集进行排序,如《排序TestExecutionListener实现》中所述。如果侦听器实现了Ordered或使用@Order进行了注释,则它会影响与默认值合并的位置。否则,在合并时将本地声明的侦听器追加到默认侦听器列表。
例如,如果前面的示例配置中的MyCustomTestExecutionListener类订单价值(例如,500)小于ServletTestExecutionListener的顺序(恰好是1000),MyCustomTestExecutionListener可以自动与违约ServletTestExecutionListener前面的列表,和前面的示例可以替换为以下:
@ContextConfiguration
@TestExecutionListeners(listeners = MyCustomTestExecutionListener.class,mergeMode = MERGE_WITH_DEFAULTS
)
class MyTest {// class body...
}
3.5.4 测试执行的事件
Spring Framework 5.2中引入的EventPublishingTestExecutionListener提供了实现自定义TestExecutionListener的替代方法。测试的ApplicationContext中的组件可以监听EventPublishingTestExecutionListener发布的以下事件,每个事件对应于TestExecutionListener API中的一个方法。
BeforeTestClassEvent
PrepareTestInstanceEvent
BeforeTestMethodEvent
BeforeTestExecutionEvent
AfterTestExecutionEvent
AfterTestMethodEvent
AfterTestClassEvent
注意:只有当ApplicationContext已经被加载时,才会发布这些事件。
这些事件可能由于各种原因被使用,例如重置模拟bean或跟踪测试执行。使用测试执行事件的一个优点而不是实现一个自定义TestExecutionListener是,测试执行的事件可以被任何Spring bean注册在测试ApplicationContext,等豆子可能受益直接从依赖注入和ApplicationContext的其他特性。相反,TestExecutionListener不是ApplicationContext中的bean。
为了监听测试执行事件,Spring bean可以选择实现org.springframe .context。ApplicationListener接口。另外,可以使用@EventListener对侦听器方法进行注释,并将其配置为侦听上面列出的特定事件类型之一(请参阅基于注释的事件侦听器)。由于这种方法的流行,Spring提供了以下专用的@EventListener注释来简化测试执行事件监听器的注册。这些注释驻留在org.springframework.test.context.event.annotation包。
@BeforeTestClass
@PrepareTestInstance
@BeforeTestMethod
@BeforeTestExecution
@AfterTestExecution
@AfterTestMethod
@AfterTestClass
异常处理
默认情况下,如果测试执行事件侦听器在使用事件时抛出异常,则该异常将传播到正在使用的底层测试框架(如JUnit或TestNG)。例如,如果使用BeforeTestMethodEvent导致异常,则相应的测试方法将由于异常而失败。相反,如果异步测试执行事件侦听器抛出异常,则该异常将不会传播到底层测试框架。有关异步异常处理的详细信息,请参考@EventListener的类级javadoc。
异步的听众
如果您需要一个特定的测试执行事件监听器来异步处理事件,您可以使用Spring的常规@Async支持。要了解更多细节,请参考@EventListener的类级别javadoc。
3.5.5 上下文管理
每个TestContext为它负责的测试实例提供上下文管理和缓存支持。测试实例不会自动接收对已配置的ApplicationContext的访问。但是,如果测试类实现了ApplicationContext接口,则会将对ApplicationContext的引用提供给测试实例。注意,AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests实现了ApplicationContext软件,因此,它们自动提供了对ApplicationContext的访问。
注意:@ autowired ApplicationContext
作为实现applicationcontext - ware接口的替代方法,您可以通过在字段或setter方法上的@Autowired注释为您的测试类注入应用程序上下文,如下例所示:
@SpringJUnitConfig
class MyTest {@Autowired ApplicationContext applicationContext;// class body...
}
类似地,如果您的测试被配置为加载WebApplicationContext,您可以将web应用程序上下文注入到您的测试中,如下所示:
@SpringJUnitWebConfig
class MyWebAppTest {@Autowired WebApplicationContext wac;// class body...
}
通过使用@Autowired实现的依赖注入是由DependencyInjectionTestExecutionListener提供的,它是默认配置的(参见测试装置的依赖注入)。
使用TestContext框架的测试类不需要扩展任何特定的类或者实现特定的接口来配置它们的应用程序上下文。相反,通过在类级别声明@ContextConfiguration注释来实现配置。如果您的测试类没有显式地声明应用程序上下文资源位置或组件类,则配置的ContextLoader将确定如何从默认位置或默认配置类加载上下文。除了上下文资源位置和组件类之外,还可以通过应用程序上下文初始化器配置应用程序上下文。
下面几节将解释如何使用Spring的@ContextConfiguration注释,通过使用XML配置文件、Groovy脚本、组件类(通常是@Configuration类)或上下文初始化器来配置测试ApplicationContext。或者,您可以为高级用例实现和配置自己的定制SmartContextLoader。
Context Configuration with XML resources
Context Configuration with Groovy Scripts
Context Configuration with Component Classes
Mixing XML, Groovy Scripts, and Component Classes
Context Configuration with Context Initializers
Context Configuration Inheritance
Context Configuration with Environment Profiles
Context Configuration with Test Property Sources
Loading a
WebApplicationContext
Context Caching
Context Hierarchies
使用XML资源进行上下文配置
要使用XML配置文件为测试加载ApplicationContext,可以使用@ContextConfiguration注释测试类,并使用包含XML配置元数据资源位置的数组配置locations属性。纯路径或相对路径(例如context.xml)被视为与定义测试类的包相关的类路径资源。以斜杠开头的路径被视为绝对类路径位置(例如,/org/example/config.xml)。表示资源URL的路径。,以classpath:、file:、http:等为前缀的路径)按原样使用。
@ExtendWith(SpringExtension.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"})
class MyTest {// class body...
}
@ContextConfiguration通过标准Java值属性为locations属性提供别名。因此,如果您不需要在@ContextConfiguration中声明额外的属性,那么您可以省略location属性名的声明,而使用下面示例中演示的简写格式来声明资源位置:
@ExtendWith(SpringExtension.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
class MyTest {// class body...
}
如果您省略了来自@ContextConfiguration注释的位置和值属性,TestContext框架将尝试检测默认的XML资源位置。具体来说,GenericXmlContextLoader和GenericXmlWebContextLoader根据测试类的名称检测默认位置。如果您的类名为com.example。GenericXmlContextLoader从“classpath:com/example/MyTest-context.xml”加载应用程序上下文。下面的例子演示了如何做到这一点:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration
class MyTest {// class body...
}
使用Groovy脚本进行上下文配置
要使用使用Groovy Bean定义DSL的Groovy脚本来为您的测试加载ApplicationContext,您可以使用@ContextConfiguration注释您的测试类,并使用包含Groovy脚本资源位置的数组来配置location或value属性。Groovy脚本的资源查找语义与XML配置文件中描述的相同。
注意:
启用Groovy脚本支持
如果Groovy位于类路径上,那么将自动启用使用Groovy脚本在Spring TestContext框架中加载ApplicationContext的支持。
下面的示例演示如何指定Groovy配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"})
class MyTest {// class body...
}
如果您省略了@ContextConfiguration注释中的位置和值属性,TestContext框架将尝试检测一个默认的Groovy脚本。具体来说,GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader根据测试类的名称检测默认位置。如果您的类名为com.example。Groovy上下文加载器从“classpath:com/example/MyTestContext.groovy”加载应用程序上下文。下面的例子展示了如何使用默认值:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration
class MyTest {// class body...
}
注意:同时声明XML配置和Groovy脚本
您可以使用@ContextConfiguration的location或value属性同时声明XML配置文件和Groovy脚本。如果到配置资源位置的路径以.xml结束,则使用XmlBeanDefinitionReader加载它。否则,使用GroovyBeanDefinitionReader加载它。
下面的清单展示了如何在集成测试中结合两者:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
class MyTest {// class body...
}
带有组件类的上下文配置
要使用组件类为测试加载ApplicationContext(请参阅基于java的容器配置),可以使用@ContextConfiguration注释测试类,并使用包含对组件类的引用的数组配置classes属性。下面的例子演示了如何做到这一点:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
class MyTest {// class body...
}
注意:
组件类
“组件类”一词可指下列任何一种:
- 一个带有@Configuration注释的类。
- 一个组件(也就是说,一个用@Component, @Service, @Repository,或者其他原型注释注释的类)。
- 一个JSR-330兼容的类,用javax注释。注入注解。
- 包含@ bean -方法的任何类。
- 打算注册为Spring组件的任何其他类(即,潜在地利用单个构造函数的自动自动装配,而不使用Spring注解。
有关组件类的配置和语义的更多信息,请参阅@Configuration和@Bean的javadoc,特别注意对@Bean Lite模式的讨论。
如果您省略了@ContextConfiguration注释中的classes属性,TestContext框架将尝试检测默认配置类的存在。具体来说,AnnotationConfigContextLoader和AnnotationConfigWebContextLoader检测测试类中满足配置类实现需求的所有静态嵌套类,如@Configuration javadoc中指定的那样。注意,配置类的名称是任意的。此外,如果需要,一个测试类可以包含多个静态嵌套配置类。在下面的示例中,OrderServiceTest类声明了一个名为Config的静态嵌套配置类,该配置类自动用于为测试类加载ApplicationContext:
@SpringJUnitConfig
// ApplicationContext will be loaded from the
// static nested Config class
class OrderServiceTest {@Configurationstatic class Config {// this bean will be injected into the OrderServiceTest class@BeanOrderService orderService() {OrderService orderService = new OrderServiceImpl();// set properties, etc.return orderService;}}@AutowiredOrderService orderService;@Testvoid testOrderService() {// test the orderService}}
从嵌套配置类加载配置信息。
混合XML、Groovy脚本和组件类
有时可能需要混合XML配置文件、Groovy脚本和组件类(通常是@Configuration类)来为测试配置ApplicationContext。例如,如果您在生产环境中使用XML配置,您可能会决定使用@Configuration类来为您的测试配置特定的spring托管组件,反之亦然。
此外,一些第三方框架(如Spring Boot)为同时从不同类型的资源加载ApplicationContext提供了一流的支持(例如,XML配置文件、Groovy脚本和@Configuration类)。从历史上看,Spring框架并不支持这种标准部署。因此,Spring框架在Spring -test模块中提供的大多数SmartContextLoader实现只支持每个测试上下文的一种资源类型。但是,这并不意味着您不能同时使用这两种方法。通用规则的一个例外是GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader同时支持XML配置文件和Groovy脚本。此外,第三方框架可能选择通过@ContextConfiguration来支持位置和类的声明,并且,使用TestContext框架中的标准测试支持,您有以下选项。
如果您想使用资源位置(例如,XML或Groovy)和@Configuration类来配置您的测试,那么您必须选择一个作为入口点,这个入口点必须包含或导入另一个入口点。例如,在XML或Groovy脚本中,您可以通过使用组件扫描或将它们定义为普通的Spring bean来包含@Configuration类,而在@Configuration类中,您可以使用@ImportResource来导入XML配置文件或Groovy脚本。注意,这个行为语义上等价于你如何配置您的应用程序在生产:在生产配置中,您定义一组XML或Groovy的资源位置或一组@ configuration类生产加载ApplicationContext,但你仍然可以自由包括或导入其他类型的配置。
使用上下文初始化器的上下文配置
要通过使用上下文初始化器来为您的测试配置一个ApplicationContext,可以使用@ContextConfiguration来注释您的测试类,并使用一个包含实现ApplicationContextInitializer的类引用的数组来配置初始化器属性。声明的上下文初始化器然后用于初始化为您的测试加载的ConfigurableApplicationContext。注意,每个声明的初始化器支持的具体的ConfigurableApplicationContext类型必须与使用中的SmartContextLoader(通常是GenericApplicationContext)创建的ApplicationContext类型兼容。此外,调用初始化器的顺序取决于它们是实现Spring的有序接口,还是使用Spring的@Order注释,还是使用标准的@Priority注释。下面的例子展示了如何使用初始化:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(classes = TestConfig.class,initializers = TestAppCtxInitializer.class)
class MyTest {// class body...
}
你也可以省略XML配置文件的声明,Groovy脚本,或组件类@ContextConfiguration完全而宣布只有ApplicationContextInitializer类,负责注册bean中——例如,通过编程的方式加载bean定义从XML文件或配置类。下面的例子演示了如何做到这一点:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class)
class MyTest {// class body...
}
上下文配置继承
@ContextConfiguration支持布尔继承位置和继承初始化器属性,这些属性表示是否应该继承由超类声明的资源位置或组件类和上下文初始化器。两个标志的默认值都为true。这意味着测试类继承了资源位置或组件类以及由任何超类声明的上下文初始化器。具体来说,测试类的资源位置或组件类被附加到由超类声明的资源位置或带注释的类的列表中。类似地,给定测试类的初始化器被添加到测试超类定义的初始化器集合中。因此,子类可以选择扩展资源位置、组件类或上下文初始化器。
如果@ContextConfiguration中的inheritLocations或inheritinitialalizer属性设置为false,则测试类的资源位置或组件类和上下文初始化器将分别被设置为shadow并有效地替换超类定义的配置。
在下一个使用XML资源位置的示例中,ExtendedTest的ApplicationContext是从base-config.xml和exten- config加载的。xml,按照这个顺序。因此,在extended-config.xml中定义的bean可以覆盖(即替换)那些在base-config.xml中定义的bean。下面的示例展示了一个类如何扩展另一个类,并同时使用自己的配置文件和超类的配置文件:
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml")
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")
class ExtendedTest extends BaseTest {// class body...
}
类似地,在下一个使用组件类的示例中,ExtendedTest的ApplicationContext是按这个顺序从BaseConfig和ExtendedConfig类加载的。因此,ExtendedConfig中定义的bean可以覆盖(即替换)那些在BaseConfig中定义的bean。下面的例子展示了一个类如何扩展另一个类,并同时使用它自己的配置类和超类的配置类:
// ApplicationContext will be loaded from BaseConfig
@SpringJUnitConfig(BaseConfig.class)
class BaseTest {// class body...
}// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@SpringJUnitConfig(ExtendedConfig.class)
class ExtendedTest extends BaseTest {// class body...
}
在下一个使用上下文初始化器的示例中,ExtendedTest的ApplicationContext是通过使用BaseInitializer和ExtendedInitializer进行初始化的。但是,请注意,调用初始化器的顺序取决于它们是实现Spring的有序接口,还是使用Spring的@Order注释,还是使用标准的@Priority注释。下面的示例展示了一个类如何扩展另一个类,并同时使用自己的初始化器和超类的初始化器:
// ApplicationContext will be initialized by BaseInitializer
@SpringJUnitConfig(initializers = BaseInitializer.class)
class BaseTest {// class body...
}// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@SpringJUnitConfig(initializers = ExtendedInitializer.class)
class ExtendedTest extends BaseTest {// class body...
}
环境配置文件的上下文配置
Spring框架对环境和概要文件的概念提供了一流的支持(又称为“bean定义概要文件”),可以配置集成测试来激活各种测试场景的特定bean定义概要文件。这是通过使用@ActiveProfiles注释一个测试类并提供一个概要文件列表来实现的,这些概要文件应该在为测试加载ApplicationContext时被激活。
注意:您可以在SmartContextLoader SPI的任何实现中使用@ActiveProfiles,但是旧的ContextLoader SPI的实现不支持@ActiveProfiles。
考虑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:scriptlocation="classpath:com/bank/config/sql/schema.sql"/><jdbc:scriptlocation="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:scriptlocation="classpath:com/bank/config/sql/schema.sql"/></jdbc:embedded-database></beans></beans>
@ExtendWith(SpringExtension.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
class TransferServiceTest {@AutowiredTransferService transferService;@Testvoid testTransferService() {// test the transferService}
}
当TransferServiceTest运行时,它的ApplicationContext从类路径根目录中的app-config.xml配置文件中加载。如果你检查app-config。在xml中,您可以看到accountRepository bean依赖于数据源bean。但是,dataSource没有定义为顶级bean。相反,数据源被定义了三次:在生产配置文件中,在开发配置文件中,在默认配置文件中。
通过使用@ActiveProfiles(“dev”)注释TransferServiceTest,我们指示Spring TestContext框架用设置为{“dev”}的活动配置文件加载ApplicationContext。结果,一个嵌入式数据库被创建并使用测试数据填充,accountRepository bean被连接到开发数据源的引用。这可能就是我们在集成测试中想要的。
有时将bean分配给默认配置文件是很有用的。只有在没有特定激活其他配置文件时,才会包括默认配置文件中的bean。您可以使用它来定义在应用程序的默认状态下使用的“回退”bean。例如,您可以显式地为开发和生产配置文件提供一个数据源,但是在这些配置文件都不活动时,将内存中的数据源定义为默认数据源。
下面的代码清单演示了如何使用@Configuration类而不是XML实现相同的配置和集成测试:
@Configuration
@Profile("dev")
public class StandaloneDataConfig {@Beanpublic 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 {@Beanpublic DataSource dataSource() {return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.HSQL).addScript("classpath:com/bank/config/sql/schema.sql").build();}
}
@Configuration
public class TransferServiceConfig {@Autowired DataSource dataSource;@Beanpublic TransferService transferService() {return new DefaultTransferService(accountRepository(), feePolicy());}@Beanpublic AccountRepository accountRepository() {return new JdbcAccountRepository(dataSource);}@Beanpublic FeePolicy feePolicy() {return new ZeroFeePolicy();}
}
@SpringJUnitConfig({TransferServiceConfig.class,StandaloneDataConfig.class,JndiDataConfig.class,DefaultDataConfig.class})
@ActiveProfiles("dev")
class TransferServiceTest {@AutowiredTransferService transferService;@Testvoid testTransferService() {// test the transferService}
}
在这个变体中,我们将XML配置分成四个独立的@Configuration类:
- TransferServiceConfig:使用@Autowired来通过依赖注入获取数据源。
- 定义适合开发人员测试的嵌入式数据库的数据源。
- JndiDataConfig:定义在生产环境中从JNDI检索的数据源。
- DefaultDataConfig:为默认的嵌入式数据库定义一个数据源,以防没有活动的配置文件。
与基于xml的配置示例一样,我们仍然使用@ActiveProfiles(“dev”)来注释TransferServiceTest,但是这次我们使用@ContextConfiguration注释来指定所有四个配置类。测试类本身的主体保持完全不变。
通常情况下,在一个给定的项目中,一组概要文件被跨多个测试类使用。因此,为了避免重复声明@ActiveProfiles注释,您可以在基类上声明一次@ActiveProfiles,子类会自动从基类继承@ActiveProfiles配置。在下面的例子中,@ActiveProfiles的声明(以及其他注释)已经被转移到一个抽象超类AbstractIntegrationTest:
@SpringJUnitConfig({TransferServiceConfig.class,StandaloneDataConfig.class,JndiDataConfig.class,DefaultDataConfig.class})
@ActiveProfiles("dev")
abstract class AbstractIntegrationTest {
}
// "dev" profile inherited from superclass
class TransferServiceTest extends AbstractIntegrationTest {@AutowiredTransferService transferService;@Testvoid testTransferService() {// test the transferService}
}
@ActiveProfiles还支持一个inheritProfiles属性,可以用来禁用活动概要文件的继承,如下面的例子所示:
// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
class ProductionTransferServiceTest extends AbstractIntegrationTest {// test body
}
此外,有时有必要以编程方式而不是声明的方式来解析测试的活动配置文件——例如,基于:
- 当前操作系统。
- 测试是否在持续集成构建服务器上执行。
- 某些环境变量的存在。
- 自定义类级注释的存在。
- 其他问题。
要以编程方式解析活动bean定义配置文件,可以实现自定义ActiveProfilesResolver,并使用@ActiveProfiles的resolver属性注册它。有关更多信息,请参见相应的javadoc。下面的例子演示了如何实现和注册一个自定义的OperatingSystemActiveProfilesResolver:
// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(resolver = OperatingSystemActiveProfilesResolver.class,inheritProfiles = false)
class TransferServiceTest extends AbstractIntegrationTest {// test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {@Overridepublic String[] resolve(Class<?> testClass) {String profile = ...;// determine the value of profile based on the operating systemreturn new String[] {profile};}
}
带有测试属性源的上下文配置
Spring框架对具有属性源层次结构的环境概念提供了一流的支持,您可以使用特定于测试的属性源配置集成测试。与@Configuration类上使用的@PropertySource注释不同,您可以在测试类上声明@TestPropertySource注释,以声明测试属性文件或内联属性的资源位置。这些测试属性源被添加到环境中的属性源集合中,这些属性源是为带注释的集成测试加载的ApplicationContext而准备的。
注意:您可以在SmartContextLoader SPI的任何实现中使用@TestPropertySource,但是旧的ContextLoader SPI的实现不支持@TestPropertySource。
SmartContextLoader的实现通过MergedContextConfiguration中的getPropertySourceLocations()和getPropertySourceProperties()方法获得对合并的测试属性源值的访问。
声明测试属性源
您可以通过使用@TestPropertySource的location或值属性来配置测试属性文件。
同时支持传统的和基于xml的属性文件格式——例如,“classpath:/com/example/test”。属性”或“文件:/ / /道路/ / file.xml”。
每个路径都被解释为一个Spring资源。普通路径(例如,“test.properties”)被视为与定义测试类的包相关的类路径资源。以斜杠开头的路径被视为绝对类路径资源(例如:“/org/example/test.xml”)。使用指定的资源协议加载引用URL的路径(例如,以classpath:、file:或http:为前缀的路径)。不允许使用资源位置通配符(例如*/.properties):每个位置必须精确计算一个.properties或.xml资源。
下面的例子使用了一个测试属性文件:
@ContextConfiguration
@TestPropertySource("/test.properties")
class MyIntegrationTests {// class body...
}
可以使用@TestPropertySource的properties属性以键-值对的形式配置内联属性,如下面的示例所示。所有键值对都作为具有最高优先级的单个测试属性源添加到封闭环境中。
键-值对支持的语法与Java属性文件中定义的条目语法相同:
key=value
key:value
key value
下面的例子设置了两个内联属性:
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"})
class MyIntegrationTests {// class body...
}
注意:从Spring Framework 5.2开始,@TestPropertySource可以用作可重复的注释。这意味着您可以在单个测试类上使用@TestPropertySource的多个声明,并且使用后面的@TestPropertySource注释中的位置和属性覆盖前面的@TestPropertySource注释中的位置和属性。
此外,您可以在一个测试类上声明多个复合注释,每个复合注释都使用@TestPropertySource进行元注释,并且所有这些@TestPropertySource声明都将有助于您的测试属性源。
直接表示@TestPropertySource注释总是优先于元表示@TestPropertySource注释。换句话说,直接来自@TestPropertySource注释的位置和属性将覆盖作为元注释使用的@TestPropertySource注释的位置和属性。
默认属性文件检测
如果@TestPropertySource被声明为空注释(也就是说,没有位置或属性属性的显式值),则尝试检测相对于声明注释的类的默认属性文件。例如,如果带注释的测试类是com.example。对应的默认属性文件是classpath:com/example/MyTest.properties。如果无法检测到默认值,则抛出IllegalStateException。
优先级
与从操作系统环境、Java系统属性或应用程序通过使用@PropertySource或编程方式声明添加的属性源加载的属性源相比,测试属性源具有更高的优先级。因此,可以使用测试属性源选择性地覆盖系统和应用程序属性源中定义的属性。此外,内联属性具有比从资源位置加载的属性更高的优先级。
在下一个示例中,时区和端口属性以及“/test”中定义的任何属性。属性“覆盖在系统和应用程序属性源中定义的任何同名属性。此外,如果“/test”。属性”文件定义时区和端口属性的条目,这些条目由使用properties属性声明的内联属性覆盖。下面的例子展示了如何在文件和内联中指定属性:
@ContextConfiguration
@TestPropertySource(locations = "/test.properties",properties = {"timezone = GMT", "port: 4242"}
)
class MyIntegrationTests {// class body...
}
继承和重写测试属性源
@TestPropertySource支持boolean inheritLocations和inheritProperties属性,它们表示属性文件的资源位置和超类声明的内联属性是否应该继承。两个标志的默认值都为true。这意味着测试类继承由任何超类声明的位置和内联属性。特别是,位置和测试类的内联属性被附加到由超类声明的位置和内联属性。因此,子类可以选择扩展位置和内联属性。注意,后面出现的属性会对前面出现的同名属性进行隐藏(即覆盖)。此外,上述优先规则也适用于继承的测试属性源。
如果@TestPropertySource中的inheritLocations或inheritProperties属性设置为false,则测试类的locations或inlinlinproperties将分别隐藏起来,从而有效地替换超类定义的配置。
在下一个示例中,仅使用base加载BaseTest的ApplicationContext。属性文件作为测试属性源。相反,ExtendedTest的ApplicationContext是通过使用基来加载的。属性和扩展。属性文件作为测试属性源位置。下面的例子展示了如何定义属性在子类和它的超类使用属性文件:
@TestPropertySource("base.properties")
@ContextConfiguration
class BaseTest {// ...
}@TestPropertySource("extended.properties")
@ContextConfiguration
class ExtendedTest extends BaseTest {// ...
}
在下一个示例中,仅使用内联的key1属性加载BaseTest的ApplicationContext。相反,ExtendedTest的ApplicationContext是通过使用内联的key1和key2属性加载的。下面的例子演示了如何使用内联属性来定义子类及其超类中的属性:
@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
class BaseTest {// ...
}@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
class ExtendedTest extends BaseTest {// ...
}
加载WebApplicationContext
要指示TestContext框架加载WebApplicationContext而不是标准的ApplicationContext,您可以使用@WebAppConfiguration注释相应的测试类。
测试类中@WebAppConfiguration的出现指示TestContext框架(TCF)应该为集成测试加载WebApplicationContext (WAC)。在后台,TCF确保创建了MockServletContext并将其提供给测试的WAC。默认情况下,MockServletContext的基本资源路径设置为src/main/webapp。这可以解释为相对于JVM根的路径(通常是到项目的路径)。如果您熟悉Maven项目中web应用程序的目录结构,那么您就知道src/main/webapp是WAR根目录的默认位置。如果需要覆盖此默认值,可以提供@WebAppConfiguration注释的替代路径(例如,@WebAppConfiguration(“src/test/webapp”))。如果希望从类路径而不是文件系统引用基本资源路径,可以使用Spring的classpath:
prefix
请注意,Spring对WebApplicationContext实现的测试支持与对标准ApplicationContext实现的支持不相上下。在使用WebApplicationContext进行测试时,您可以使用@ContextConfiguration声明XML配置文件、Groovy脚本或@Configuration类。您还可以自由地使用任何其他的测试注释,比如@ActiveProfiles、@ testexecutionlistener、@Sql、@Rollback等。
本节中的其余示例展示了加载WebApplicationContext的各种配置选项。下面的例子展示了TestContext框架对约定优于配置的支持:
@ExtendWith(SpringExtension.class)// defaults to "file:src/main/webapp"
@WebAppConfiguration// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration
class WacTests {//...
}
如果您使用@WebAppConfiguration注释一个测试类,而没有指定资源基路径,那么资源路径实际上默认为file:src/main/webapp。类似地,如果您声明@ContextConfiguration而没有指定资源位置、组件类或上下文初始化器,Spring将尝试使用约定(即WacTests-context.xml与WacTests类或静态嵌套的@Configuration类位于同一个包中)来检测您的配置是否存在。
下面的示例显示了如何使用@WebAppConfiguration显式地声明资源基路径,以及使用@ContextConfiguration显式地声明XML资源位置:
@ExtendWith(SpringExtension.class)// file system resource
@WebAppConfiguration("webapp")// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")
class WacTests {//...
}
这里需要注意的重要一点是,使用这两个注释的路径具有不同的语义。默认情况下,@WebAppConfiguration资源路径是基于文件系统的,而@ContextConfiguration资源位置是基于类路径的。
下面的例子表明,我们可以通过指定Spring资源前缀来覆盖这两个注释的默认资源语义:
@ExtendWith(SpringExtension.class)// classpath resource
@WebAppConfiguration("classpath:test-web-resources")// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")
class WacTests {//...
}
将本例中的注释与前面的示例进行对比。
使用Web模拟
为了提供全面的web测试支持,TestContext框架有一个默认启用的ServletTestExecutionListener。当测试WebApplicationContext,这TestExecutionListener设置默认线程局部状态使用Spring Web的RequestContextHolder在每个测试方法之前,创建一个MockHttpServletRequest, MockHttpServletResponse, ServletWebRequest配置@WebAppConfiguration基于基础资源路径。ServletTestExecutionListener还确保可以将MockHttpServletResponse和ServletWebRequest注入到测试实例中,并且一旦测试完成,它就清理线程本地状态。
一旦您为您的测试加载了WebApplicationContext,您可能会发现您需要与web模拟进行交互——例如,设置您的测试装置或者在调用您的web组件后执行断言。下面的示例显示了哪些模拟可以自动装配到您的测试实例中。注意,WebApplicationContext和MockServletContext都缓存在测试套件中,而其他模拟则由ServletTestExecutionListener根据测试方法进行管理。
@SpringJUnitWebConfig
class WacTests {@AutowiredWebApplicationContext wac; // cached@AutowiredMockServletContext servletContext; // cached@AutowiredMockHttpSession session;@AutowiredMockHttpServletRequest request;@AutowiredMockHttpServletResponse response;@AutowiredServletWebRequest webRequest;//...
}
上下文缓存
一旦TestContext框架为一个测试加载了一个ApplicationContext(或WebApplicationContext),这个上下文就会被缓存,并被所有后续的在同一个测试套件中声明相同的唯一上下文配置的测试重用。为了理解缓存是如何工作的,理解“惟一”和“测试套件”的含义是很重要的。
ApplicationContext可以通过用于加载它的配置参数组合唯一地标识。因此,使用配置参数的唯一组合来生成用于缓存上下文的键。TestContext框架使用以下配置参数来构建上下文缓存键:
locations
(from@ContextConfiguration
)classes
(from@ContextConfiguration
)contextInitializerClasses
(from@ContextConfiguration
)contextCustomizers
(fromContextCustomizerFactory
)contextLoader
(from@ContextConfiguration
)parent
(from@ContextHierarchy
)activeProfiles
(from@ActiveProfiles
)propertySourceLocations
(from@TestPropertySource
)propertySourceProperties
(from@TestPropertySource
)resourceBasePath
(from@WebAppConfiguration
)
例如,如果TestClassA指定{"app-config.xml”、“test-config.xml" 对于@ContextConfiguration的locations(或值)属性,TestContext框架加载相应的ApplicationContext,并将其存储在静态上下文缓存中,该缓存的键仅基于这些位置。因此,如果TestClassB也定义了{"app-config.xml", "test-config.xml" 对于其位置(通过继承显式或隐式地),但是不定义@WebAppConfiguration、不同的ContextLoader、不同的活动配置文件、不同的上下文初始化器、不同的测试属性源或不同的父上下文,那么两个测试类共享相同的ApplicationContext。这意味着加载应用程序上下文的设置成本只发生一次(每个测试套件),并且后续的测试执行要快得多。
注意:
测试套件和分叉的流程
Spring TestContext框架将应用程序上下文存储在静态缓存中。这意味着上下文实际上存储在一个静态变量中。换句话说,如果测试在单独的进程中执行,那么在每个测试执行之间将清除静态缓存,这将有效地禁用缓存机制。
为了从缓存机制中获益,所有测试必须在相同的流程或测试套件中运行。这可以通过在IDE中作为一个组执行所有测试来实现。类似地,在使用构建框架(如Ant、Maven或Gradle)执行测试时,务必确保构建框架不会在测试之间转移。例如,如果Maven Surefire插件的forkMode被设置为always或pertest,那么TestContext框架就不能在测试类之间缓存应用程序上下文,因此构建过程的运行速度会明显变慢。
上下文缓存的大小以默认的最大大小32为界限。每当达到最大大小时,就使用最少最近使用(LRU)的清除策略来驱逐和关闭陈旧的上下文。您可以通过设置一个名为spring.test.context.cache.maxSize的JVM系统属性,从命令行或构建脚本中配置最大大小。作为替代方案,您可以通过使用SpringProperties API以编程方式设置相同的属性。
由于在给定的测试套件中加载了大量的应用程序上下文可能会导致该套件花费不必要的时间来执行,因此准确地知道已经加载和缓存了多少上下文通常是有益的。要查看底层上下文缓存的统计信息,您可以设置org.springframework.test.context的日志级别。要调试的缓存日志记录类别。
在可能的情况下,一个测试应用程序上下文导致腐败,需要重新加载(例如,通过修改一个bean定义或应用程序对象的状态),你可以用@DirtiesContext注释您的测试类或测试方法(见@DirtiesContext @DirtiesContext)的讨论。这指示Spring从缓存中删除上下文并在运行下一个需要相同应用程序上下文的测试之前重新构建应用程序上下文。注意,对@DirtiesContext注释的支持是由DirtiesContextBeforeModesTestExecutionListener和DirtiesContextTestExecutionListener提供的,它们在默认情况下是启用的。
上下文层次结构
在编写依赖于加载的Spring ApplicationContext的集成测试时,通常只需要针对单个上下文进行测试就足够了。但是,有时根据ApplicationContext实例的层次结构进行测试是有益的,甚至是必要的。例如,如果您正在开发一个Spring MVC web应用程序,您通常有一个由Spring的ContextLoaderListener加载的根WebApplicationContext和一个由Spring的DispatcherServlet加载的子WebApplicationContext。这导致了父子上下文层次结构,其中共享组件和基础设施配置在根上下文中声明,并由特定于web的组件在子上下文中使用。另一个用例可以在Spring批处理应用程序中找到,其中通常有一个为共享批处理基础结构提供配置的父上下文和一个为特定批处理作业配置的子上下文。
您可以编写使用上下文层次结构的集成测试,方法是在单个测试类或测试类层次结构中使用@ context thier阿其注释声明上下文配置。如果在一个测试类层次结构中的多个类上声明了一个上下文层次结构,那么您还可以合并或覆盖上下文层次结构中指定的指定级别的上下文配置。在为层次结构中的给定级别合并配置时,配置资源类型(即XML配置文件或组件类)必须是一致的。否则,在上下文层次结构中使用不同的资源类型配置不同的级别是完全可以接受的。
本节中其余的基于JUnit Jupiter的示例展示了集成测试的常见配置场景,这些场景需要使用上下文层次结构。
带有上下文层次结构的单个测试类
ControllerIntegrationTests代表一个典型的集成测试场景的Spring MVC web应用程序通过声明一个上下文层次结构,由两个水平,一根WebApplicationContext(由使用TestAppConfig @ configuration类加载),一个用于dispatcher servlet WebApplicationContext(由使用WebConfig @ configuration类加载)。被自动导入测试实例的WebApplicationContext是用于子上下文的上下文(即,层次结构中最低的上下文)。下面的清单显示了这个配置场景:
@ExtendWith(SpringExtension.class)
@WebAppConfiguration
@ContextHierarchy({@ContextConfiguration(classes = TestAppConfig.class),@ContextConfiguration(classes = WebConfig.class)
})
class ControllerIntegrationTests {@AutowiredWebApplicationContext wac;// ...
}
具有隐式父上下文的类层次结构
本例中的测试类定义了测试类层次结构中的上下文层次结构。AbstractWebTests在spring支持的web应用程序中声明根WebApplicationContext的配置。但是请注意,AbstractWebTests并没有声明@上下文层次结构。因此,AbstractWebTests的子类可以选择参与上下文层次结构,或者遵循@ContextConfiguration的标准语义。SoapWebServiceTests和RestWebServiceTests都对AbstractWebTests进行了扩展,并使用@ context层次结构定义了上下文层次结构。结果是加载了三个应用程序上下文(每个@ContextConfiguration声明一个上下文),基于AbstractWebTests中的配置加载的应用程序上下文被设置为为具体子类加载的每个上下文的父上下文。下面的清单显示了这个配置场景:
@ExtendWith(SpringExtension.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 {}
具有合并上下文层次结构配置的类层次结构
本例中的类显示了使用命名层次结构层次来合并上下文层次结构中特定层次的配置。BaseTests在层次结构中定义了两个级别,父级和子级。ExtendedTests扩展了基测试,并指示Spring TestContext框架合并子层次结构级别的上下文配置,方法是确保在@ContextConfiguration的name属性中声明的名称都是子元素。结果是加载了三个应用程序上下文:一个for /app-config。xml,一个for /user-config。和一个用于{"/user-config。xml”、“/ order-config.xml”}。与前面的示例一样,从/app-config.xml加载的应用程序上下文被设置为从/user-config.xml和{"/user-config. xml "加载的上下文的父上下文。xml”、“/ order-config.xml”}。下面的清单显示了这个配置场景:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({@ContextConfiguration(name = "parent", locations = "/app-config.xml"),@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}@ContextHierarchy(@ContextConfiguration(name = "child", locations = "/order-config.xml")
)
class ExtendedTests extends BaseTests {}
类层次结构,具有重写的上下文层次结构配置
与前面的示例不同,这个示例演示了如何通过将@ContextConfiguration中的inheritLocations标记设置为false来覆盖上下文层次结构中给定的指定级别的配置。因此,ExtendedTests的应用程序上下文仅从/test-user config.xml加载,并将其父设置为从/app-config.xml加载的上下文。下面的清单显示了这个配置场景:
@ExtendWith(SpringExtension.class)
@ContextHierarchy({@ContextConfiguration(name = "parent", locations = "/app-config.xml"),@ContextConfiguration(name = "child", locations = "/user-config.xml")
})
class BaseTests {}@ContextHierarchy(@ContextConfiguration(name = "child",locations = "/test-user-config.xml",inheritLocations = false
))
class ExtendedTests extends BaseTests {}
注意:
在上下文层次结构中清除上下文
如果您在测试中使用@DirtiesContext,而测试的上下文被配置为上下文层次结构的一部分,那么您可以使用hierarchyMode标志来控制上下文缓存的清除方式。有关更多细节,请参见Spring测试注释中的@DirtiesContext和@DirtiesContext javadoc中的讨论。
3.5.6 测试夹具的依赖注入
当您使用DependencyInjectionTestExecutionListener(它是默认配置的)时,您的测试实例的依赖关系是从您用@ContextConfiguration或相关注释配置的应用程序上下文中的bean注入的。您可以使用setter注入,字段注入,或者两者都使用,这取决于您选择的注释以及您是否将它们放在setter方法或字段上。如果您正在使用JUnit Jupiter,您也可以选择使用构造函数注入(参见SpringExtension的依赖注入)。为了与Spring基于注解的注入支持保持一致,您还可以使用Spring的@Autowired注解或者JSR-330中的@Inject注解来进行字段和setter注入。
注意:对于JUnit Jupiter以外的测试框架,TestContext框架不参与测试类的实例化。因此,对构造函数使用@Autowired或@Inject对测试类没有影响。
虽然在生产代码中不鼓励字段注入,但在测试代码中字段注入实际上是很自然的。区别的基本原理是您永远不会直接实例化您的测试类。因此,不需要能够调用测试类上的公共构造函数或setter方法。
因为@Autowired被用来按类型执行自动装配,如果你有多个相同类型的bean定义,你就不能依靠这个方法来处理那些特定的bean。在这种情况下,您可以将@Autowired与@Qualifier结合使用。您还可以选择将@Inject与@Named结合使用。或者,如果您的测试类可以访问它的ApplicationContext,那么您可以使用(例如)对ApplicationContext的调用来执行显式查找。getBean (“titleRepository TitleRepository.class)。
如果您不希望依赖项注入应用于您的测试实例,那么不要使用@Autowired或@Inject来注释字段或setter方法。或者,您可以通过显式地使用@ testexecutionlistener配置您的类,并从侦听器列表中删除DependencyInjectionTestExecutionListener.class,从而完全禁用依赖项注入。
考虑一下测试HibernateTitleRepository类的场景,如目标部分所述。接下来的两个代码清单演示了如何在字段和setter方法上使用@Autowired。应用程序上下文配置在所有示例代码清单之后显示。
注意:
以下代码清单中的依赖项注入行为并不特定于JUnit Jupiter。同样的DI技术可以与任何受支持的测试框架一起使用。
下面的示例调用静态断言方法,如assertNotNull(),但不使用断言作为调用的前缀。在这种情况下,假设该方法是通过import静态声明正确导入的,而该静态声明在示例中没有显示。
第一个代码清单显示了一个基于JUnit Jupiter的测试类实现,它使用@Autowired进行字段注入:
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {// this instance will be dependency injected by type@AutowiredHibernateTitleRepository titleRepository;@Testvoid findById() {Title title = titleRepository.findById(new Long(10));assertNotNull(title);}
}
或者,您可以配置类为使用@Autowired进行setter注入,如下所示:
@ExtendWith(SpringExtension.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
class HibernateTitleRepositoryTests {// this instance will be dependency injected by typeHibernateTitleRepository titleRepository;@Autowiredvoid setTitleRepository(HibernateTitleRepository titleRepository) {this.titleRepository = titleRepository;}@Testvoid findById() {Title title = titleRepository.findById(new Long(10));assertNotNull(title);}
}
前面的代码清单使用与@ContextConfiguration注释(即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/beanshttps://www.springframework.org/schema/beans/spring-beans.xsd"><!-- this bean will be injected into the HibernateTitleRepositoryTests class --><bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository"><property name="sessionFactory" ref="sessionFactory"/></bean><bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"><!-- configuration elided for brevity --></bean></beans>
注意:
如果您从spring提供的测试基类扩展,而这个测试基类碰巧在其setter方法上使用@Autowired,那么您可能在应用程序上下文中定义了多个受影响类型的bean(例如,多个数据源bean)。在这种情况下,您可以覆盖setter方法,并使用@Qualifier注释来指示特定的目标bean,如下所示(但也要确保委派给超类中被覆盖的方法):
// ...@Autowired@Overridepublic void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {super.setDataSource(dataSource);}// ...
指定的限定符值指示要注入的特定数据源bean,从而将类型匹配集缩小到特定bean。它的值与对应的<bean>定义中的<限定符>声明相匹配。bean名称用作回退限定符值,因此您还可以通过名称有效地指向一个特定的bean(如前面所示,假设myDataSource是bean id)。
3.5.7 测试请求和会话范围的bean
从早期开始,Spring就支持请求作用域bean和会话作用域bean,您可以通过以下步骤测试请求作用域bean和会话作用域bean:
- 通过使用@WebAppConfiguration注释测试类,确保为您的测试加载了WebApplicationContext。
- 将模拟请求或会话注入到测试实例中,并根据需要准备测试装置。
- 调用从配置的WebApplicationContext中检索的web组件(使用依赖项注入)。
- 针对模拟执行断言。
下一个代码片段显示了登录用例的XML配置。请注意,userService bean依赖于请求范围的loginAction bean。另外,LoginAction通过使用SpEL表达式实例化,SpEL表达式从当前HTTP请求检索用户名和密码。在我们的测试中,我们希望通过TestContext框架管理的模拟来配置这些请求参数。下面的清单显示了这个用例的配置:
请求范围内bean配置
<beans><bean id="userService" class="com.example.SimpleUserService"c:loginAction-ref="loginAction"/><bean id="loginAction" class="com.example.LoginAction"c:username="#{request.getParameter('user')}"c:password="#{request.getParameter('pswd')}"scope="request"><aop:scoped-proxy/></bean></beans>
在RequestScopedBeanTests中,我们将UserService(即被测试的主题)和MockHttpServletRequest注入到我们的测试实例中。在我们的requestScope()测试方法中,我们通过在提供的MockHttpServletRequest中设置请求参数来设置测试装置。当在我们的userService上调用loginUser()方法时,我们确信用户服务可以访问当前MockHttpServletRequest的请求范围的loginAction(也就是说,我们只是在其中设置参数)。然后,我们可以根据已知的用户名和密码输入对结果执行断言。下面的清单显示了如何做到这一点:
@SpringJUnitWebConfig
class RequestScopedBeanTests {@Autowired UserService userService;@Autowired MockHttpServletRequest request;@Testvoid requestScope() {request.setParameter("user", "enigma");request.setParameter("pswd", "$pr!ng");LoginResults results = userService.loginUser();// assert results}
}
下面的代码片段与我们前面看到的用于请求作用域的bean的代码片段类似。但是,这一次,userService bean依赖于会话范围的userPreferences bean。请注意,UserPreferences bean是通过使用从当前HTTP会话检索主题的SpEL表达式来实例化的。在我们的测试中,我们需要在TestContext框架管理的模拟会话中配置一个主题。下面的例子演示了如何做到这一点:
会话范围内的bean配置
<beans><bean id="userService" class="com.example.SimpleUserService"c:userPreferences-ref="userPreferences" /><bean id="userPreferences" class="com.example.UserPreferences"c:theme="#{session.getAttribute('theme')}"scope="session"><aop:scoped-proxy/></bean></beans>
在SessionScopedBeanTests中,我们将UserService和MockHttpSession注入到我们的测试实例中。在我们的sessionScope()测试方法中,我们通过在提供的MockHttpSession中设置预期的主题属性来设置测试装置。在userService上调用processUserPreferences()方法时,我们确信用户服务可以访问当前MockHttpSession的会话范围的userPreferences,并且我们可以根据配置的主题对结果执行断言。下面的例子演示了如何做到这一点:
@SpringJUnitWebConfig
class SessionScopedBeanTests {@Autowired UserService userService;@Autowired MockHttpSession session;@Testvoid sessionScope() throws Exception {session.setAttribute("theme", "blue");Results results = userService.processUserPreferences();// assert results}
}
3.5.8. Transaction Management
在TestContext框架中,事务由TransactionalTestExecutionListener管理,这是默认配置的,即使您没有在测试类上显式地声明@ testexecutionlistener。然而,要启用对事务的支持,您必须在ApplicationContext中配置一个PlatformTransactionManager bean,该bean使用@ContextConfiguration语义加载(稍后将提供进一步的详细信息)。此外,您必须在测试的类或方法级别声明Spring的@Transactional注释。
Test-managed事务
测试管理的事务是通过使用TransactionalTestExecutionListener以声明方式管理的事务,或者通过使用TestTransaction(稍后将进行描述)以编程方式管理的事务。您不应该将这些事务与Spring管理的事务(那些由Spring在为测试加载的ApplicationContext中直接管理的事务)或应用程序管理的事务(那些由测试调用的应用程序代码中以编程方式管理的事务)相混淆。spring管理的事务和应用程序管理的事务通常参与测试管理的事务。但是,如果spring管理的或应用程序管理的事务配置了任何传播类型,而不是必需的或支持的,则应该谨慎使用(有关详细信息,请参阅关于transaction propagation的讨论)。
警告:抢占式超时和测试管理的事务
在与Spring的测试管理事务一起使用测试框架的任何形式的抢占式超时时,必须谨慎。
具体来说,Spring的测试支持将事务状态绑定到当前线程(通过java.lang实现)。在调用当前测试方法之前。如果测试框架在新线程中调用当前测试方法以支持抢占式超时,则在当前测试方法中执行的任何操作都不会在测试管理的事务中调用。因此,任何此类操作的结果都不会在测试管理的事务中回滚。相反,即使Spring正确地回滚了测试管理的事务,这些操作也将提交到持久存储(例如关系数据库)。
这种情况包括但不限于以下情况。
- JUnit 4的@Test(timeout =…)支持和超时规则
- JUnit Jupiter的asserttimeoutpreemp(…)方法。断言类
- TestNG的@Test(timeOut =…)支持
启用和禁用事务
使用@Transactional注释测试方法将导致在事务中运行测试,默认情况下,事务在测试完成后自动回滚。如果一个测试类被@Transactional注释,那么类层次结构中的每个测试方法都在一个事务中运行。没有使用@Transactional注释的测试方法(在类或方法级别)不会在事务中运行。注意,测试生命周期方法不支持@Transactional——例如,用JUnit Jupiter的@BeforeAll、@BeforeEach等注释的方法。而且,使用@Transactional注释但将传播属性设置为NOT_SUPPORTED的测试不会在事务中运行。
Attribute | Supported for test-managed transactions |
---|---|
|
yes |
|
only |
|
no |
|
no |
|
no |
|
no: use |
|
no: use |
注意:方法级生命周期方法——例如,用JUnit Jupiter的@BeforeEach或@AfterEach注释的方法——在一个测试管理的事务中运行。另一方面,suite-level和class-level生命周期方法—例如,用JUnit Jupiter的@BeforeAll或@AfterAll注释的方法和用TestNG的@BeforeSuite、@AfterSuite、@BeforeClass或@AfterClass注释的方法—不在测试管理的事务中运行。
如果您需要在事务中执行suite级或类级生命周期方法中的代码,您可能希望将相应的PlatformTransactionManager注入到您的测试类中,然后将其与TransactionTemplate一起用于程序化事务管理。
注意,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests是预先配置的,用于类级别的事务支持。
下面的例子演示了一个为基于hibernate的用户库编写集成测试的常见场景:
@SpringJUnitConfig(TestConfig.class)
@Transactional
class HibernateUserRepositoryTests {@AutowiredHibernateUserRepository repository;@AutowiredSessionFactory sessionFactory;JdbcTemplate jdbcTemplate;@Autowiredvoid setDataSource(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}@Testvoid createUser() {// track initial state in test database:final int count = countRowsInTable("user");User user = new User(...);repository.save(user);// Manual flush is required to avoid false positive in testsessionFactory.getCurrentSession().flush();assertNumUsers(count + 1);}private int countRowsInTable(String tableName) {return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);}private void assertNumUsers(int expected) {assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));}
}
正如在事务回滚和提交行为中所解释的,在createUser()方法运行后不需要清理数据库,因为对数据库所做的任何更改都会被TransactionalTestExecutionListener自动回滚。
事务回滚和提交行为
默认情况下,测试事务将在测试完成后自动回滚;然而,事务性提交和回滚行为可以通过@Commit和@Rollback注释进行声明性配置。有关详细信息,请参阅注释支持部分中的相应条目。
编程式事务管理
您可以通过使用TestTransaction中的静态方法以编程方式与测试管理的事务进行交互。例如,您可以在测试方法内部、方法之前和方法之后使用TestTransaction来启动或结束当前的测试管理事务,或者配置当前的测试管理事务以进行回滚或提交。只要启用了TransactionalTestExecutionListener,就会自动提供对TestTransaction的支持。
下面的示例演示了TestTransaction的一些特性。有关更多细节,请参见TestTransaction的javadoc。
@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extendsAbstractTransactionalJUnit4SpringContextTests {@Testpublic void transactionalTest() {// assert initial state in test database:assertNumUsers(2);deleteFromTables("user");// changes to the database will be committed!TestTransaction.flagForCommit();TestTransaction.end();assertFalse(TestTransaction.isActive());assertNumUsers(0);TestTransaction.start();// perform other actions against the database that will// be automatically rolled back after the test completes...}protected void assertNumUsers(int expected) {assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));}
}
在事务外部运行代码
有时候,您可能需要事务之前或之后执行某些代码测试方法但在事务上下文——例如,验证初始数据库状态之前运行您的测试或验证预期的事务提交行为测试运行后(如果测试配置提交事务)。TransactionalTestExecutionListener支持@BeforeTransaction和@AfterTransaction注释。您可以使用这些注释之一来注释测试类中的任何void方法或测试接口中的任何void默认方法,而TransactionalTestExecutionListener将确保您的事务前方法或事务后方法在适当的时间运行。
注意:任何before方法(例如用JUnit Jupiter的@BeforeEach注释的方法)和任何after方法(例如用JUnit Jupiter的@AfterEach注释的方法)都在一个事务中运行。此外,对于没有配置为在事务中运行的测试方法,不会运行@BeforeTransaction或@AfterTransaction注释的方法。
配置事务管理器
TransactionalTestExecutionListener期望在Spring ApplicationContext中为测试定义一个PlatformTransactionManager bean。如果在测试的ApplicationContext中有多个PlatformTransactionManager实例,您可以使用@Transactional(“myTxMgr”)或@Transactional(transactionManager =“myTxMgr”)声明一个限定符,或者使用@Configuration类实现TransactionManagementConfigurer。有关用于在测试的ApplicationContext中查找事务管理器的算法的详细信息,请参考TestContextTransactionUtils.retrieveTransactionManager()的javadoc。
演示所有与事务相关的注释
下面基于JUnit Jupiter的示例显示了一个虚构的集成测试场景,其中突出显示了所有与事务相关的注释。这个示例并不打算演示最佳实践,而是演示如何使用这些注释。有关更多信息和配置示例,请参阅注释支持部分。@Sql的事务管理包含一个附加示例,该示例使用@Sql执行具有默认事务回滚语义的声明性SQL脚本。下面的例子展示了相关的注释:
@SpringJUnitConfig
@Transactional(transactionManager = "txMgr")
@Commit
class FictitiousTransactionalTest {@BeforeTransactionvoid verifyInitialDatabaseState() {// logic to verify the initial state before a transaction is started}@BeforeEachvoid setUpTestDataWithinTransaction() {// set up test data within the transaction}@Test// overrides the class-level @Commit setting@Rollbackvoid modifyDatabaseWithinTransaction() {// logic which uses the test data and modifies database state}@AfterEachvoid tearDownWithinTransaction() {// execute "tear down" logic within the transaction}@AfterTransactionvoid verifyFinalDatabaseState() {// logic to verify the final state after transaction has rolled back}}
注意:
在测试ORM代码时避免误报
当您测试操作Hibernate会话或JPA持久性上下文状态的应用程序代码时,请确保在运行该代码的测试方法中刷新底层工作单元。未能清除底层工作单元可能会产生误报:测试通过,但是相同的代码在活动的生产环境中抛出异常。请注意,这适用于维护内存中工作单元的任何ORM框架。在下面的基于hibernate的示例测试用例中,一种方法显示假阳性,另一种方法正确显示刷新会话的结果:
// ...@Autowired
SessionFactory sessionFactory;@Transactional
@Test // no expected exception!
public void falsePositive() {updateEntityInHibernateSession();// False positive: an exception will be thrown once the Hibernate// Session is finally flushed (i.e., in production code)
}@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {updateEntityInHibernateSession();// Manual flush is required to avoid false positive in testsessionFactory.getCurrentSession().flush();
}// ...
下面的例子显示了JPA的匹配方法:
// ...@PersistenceContext
EntityManager entityManager;@Transactional
@Test // no expected exception!
public void falsePositive() {updateEntityInJpaPersistenceContext();// False positive: an exception will be thrown once the JPA// EntityManager is finally flushed (i.e., in production code)
}@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {updateEntityInJpaPersistenceContext();// Manual flush is required to avoid false positive in testentityManager.flush();
}// ...
3.5.9. Executing SQL Scripts
在对关系数据库编写集成测试时,执行SQL脚本来修改数据库模式或将测试数据插入表中通常是有益的。Spring -jdbc模块支持通过在加载Spring ApplicationContext时执行SQL脚本来初始化嵌入式或现有数据库。有关详细信息,请参阅嵌入式数据库支持和使用嵌入式数据库测试数据访问逻辑。
虽然在加载ApplicationContext时初始化数据库以进行测试非常有用,但有时在集成测试期间修改数据库是必要的。下面几节将解释如何在集成测试期间以编程方式和声明方式执行SQL脚本。
以编程方式执行SQL脚本
Spring为在集成测试方法中以编程方式执行SQL脚本提供了以下选项。
- org.springframework.jdbc.datasource.init.ScriptUtils
- org.springframework.jdbc.datasource.init.ResourceDatabasePopulator
- org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests
- org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests
ScriptUtils提供了一组用于处理SQL脚本的静态实用程序方法,主要用于框架内部。但是,如果您需要完全控制SQL脚本的解析和执行方式,那么ScriptUtils可能比后面介绍的其他替代方法更适合您的需要。有关更多详细信息,请参阅ScriptUtils中的各个方法的javadoc。
ResourceDatabasePopulator提供了一个基于对象的API,用于使用外部资源中定义的SQL脚本以编程方式填充、初始化或清理数据库。ResourceDatabasePopulator提供用于配置字符编码、语句分隔符、注释分隔符和解析和运行脚本时使用的错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参阅javadoc。要运行ResourceDatabasePopulator中配置的脚本,您可以调用populate(连接)方法来对java.sql执行populator。连接或执行(DataSource)方法来针对javax.sql.DataSource执行填充程序。下面的示例为测试模式和测试数据指定SQL脚本,将语句分隔符设置为@@,并针对数据源执行脚本:
@Test
void databaseTest() {ResourceDatabasePopulator populator = new ResourceDatabasePopulator();populator.addScripts(new ClassPathResource("test-schema.sql"),new ClassPathResource("test-data.sql"));populator.setSeparator("@@");populator.execute(this.dataSource);// execute code that uses the test schema and data
}
注意ResourceDatabasePopulator在内部委托给ScriptUtils来解析和运行SQL脚本。类似地,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)方法在内部使用ResourceDatabasePopulator运行SQL脚本。有关更多详细信息,请参阅各种executeSqlScript(..)方法的javadoc。
使用@Sql声明性地执行SQL脚本
除了前面提到的以编程方式运行SQL脚本的机制之外,您还可以声明性地在Spring TestContext框架中配置SQL脚本。具体来说,您可以在测试类或测试方法上声明@Sql注释,以配置各个SQL语句或SQL脚本的资源路径,这些脚本应该在集成测试方法之前或之后在给定的数据库上运行。SqlScriptsTestExecutionListener提供了对@Sql的支持,它在默认情况下是启用的。
注意:方法级@Sql声明默认情况下覆盖类级声明。但是,从Spring Framework 5.2开始,可以通过@SqlMergeMode为每个测试类或每个测试方法配置此行为。有关详细信息,请参见使用@SqlMergeMode合并和覆盖配置。
path资源的语义
每个路径都被解释为一个Spring资源。普通路径(例如,“schema.sql”)被视为与定义测试类的包相关的类路径资源。以斜杠开头的路径被视为绝对类路径资源(例如,“/org/example/schema.sql”)。引用URL的路径(例如,以classpath:、file:、http:为前缀的路径)是通过使用指定的资源协议加载的。
下面的示例展示了如何在基于JUnit Jupiter的集成测试类的类级和方法级使用@Sql:
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {@Testvoid emptySchemaTest() {// execute code that uses the test schema without any test data}@Test@Sql({"/test-schema.sql", "/test-user-data.sql"})void userTest() {// execute code that uses the test schema and test data}
}
默认的脚本检测
如果没有指定SQL脚本或语句,则根据@Sql声明的位置尝试检测默认脚本。如果无法检测到默认值,则抛出IllegalStateException。
- 类级别声明:如果带注释的测试类是com.example。对应的默认脚本是classpath:com/example/MyTest.sql。
- 方法级声明:如果带注释的测试方法名为testMethod(),并在com.example类中定义。对应的默认脚本是classpath:com/example/MyTest.testMethod.sql。
声明多个@Sql集合
如果您需要为一个给定的测试类或测试方法配置多个SQL脚本集,但是使用不同的语法配置、不同的错误处理规则或每个集的不同执行阶段,那么您可以声明@Sql的多个实例。使用Java 8,可以使用@Sql作为可重复的注释。否则,可以使用@SqlGroup注释作为显式容器来声明@Sql的多个实例。
下面的示例展示了如何使用@Sql作为Java 8的可重复注释:
@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
void userTest() {// execute code that uses the test schema and test data
}
在前面的示例场景中,测试模式。sql脚本对单行注释使用不同的语法。
下面的示例与前面的示例相同,不同之处在于@Sql声明被分组在@SqlGroup中。对于Java 8及以上版本,使用@SqlGroup是可选的,但是为了与其他JVM语言(如Kotlin)兼容,您可能需要使用@SqlGroup。
@Test
@SqlGroup({@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),@Sql("/test-user-data.sql")
)}
void userTest() {// execute code that uses the test schema and test data
}
脚本执行阶段
默认情况下,SQL脚本在相应的测试方法之前执行。但是,如果您需要在测试方法之后运行一组特定的脚本(例如,清理数据库状态),您可以使用@Sql中的executionPhase属性,如下面的示例所示:
@Test
@Sql(scripts = "create-test-data.sql",config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(scripts = "delete-test-data.sql",config = @SqlConfig(transactionMode = ISOLATED),executionPhase = AFTER_TEST_METHOD
)
void userTest() {// execute code that needs the test data to be committed// to the database outside of the test's transaction
}
注意,隔离和AFTER_TEST_METHOD是从Sql静态导入的。TransactionMode和Sql。分别ExecutionPhase。
使用@SqlConfig进行脚本配置
可以使用@SqlConfig注释配置脚本解析和错误处理。当声明为集成测试类上的类级注释时,@SqlConfig充当测试类层次结构中所有SQL脚本的全局配置。当使用@Sql注释的config属性直接声明时,@SqlConfig充当在封闭的@Sql注释中声明的SQL脚本的本地配置。@SqlConfig中的每个属性都有一个隐式的默认值,该值记录在相应属性的javadoc中。由于Java语言规范中为注释属性定义的规则,不幸的是,不可能将null值赋给注释属性。因此,为了支持对继承的全局配置的覆盖,@SqlConfig属性的显式默认值为“”(用于字符串)、{}(用于数组)或default(用于枚举)。这种方法允许@SqlConfig的局部声明选择性地覆盖@SqlConfig的全局声明中的单个属性,方法是提供一个不同于“”、{}或默认值的值。当本地@SqlConfig属性不提供“”、{}或默认值以外的显式值时,将继承全局@SqlConfig属性。因此,显式本地配置覆盖全局配置。
@Sql和@SqlConfig提供的配置选项与ScriptUtils和ResourceDatabasePopulator支持的配置选项相同,但是是<jdbc:initialize-database/> XML namespace元素提供的配置选项的超集。有关详细信息,请参见@Sql和@SqlConfig中各个属性的javadoc。
@Sql的事务管理
默认情况下,SqlScriptsTestExecutionListener为使用@Sql配置的脚本推断所需的事务语义。具体来说,SQL脚本运行没有交易,在现有spring管理事务(例如,事务管理的TransactionalTestExecutionListener测试与@ transactional注释),或在一个孤立的事务,这取决于transactionMode属性的配置价值@SqlConfig和PlatformTransactionManager测试的ApplicationContext的存在。但是,最基本的要求是javax.sql。数据源必须出现在测试的ApplicationContext中。
如果SqlScriptsTestExecutionListener用于检测数据源和平台transactionManager并推断事务语义的算法不适合您的需要,您可以通过设置@SqlConfig的数据源和transactionManager属性来指定显式名称。此外,您可以通过设置@SqlConfig的transactionMode属性来控制事务传播行为(例如,脚本是否应该在独立的事务中运行)。虽然对所有支持的全面讨论选择事务管理与@Sql超出了这个范围参考手册,@SqlConfig javadoc和SqlScriptsTestExecutionListener提供详细信息,和下面的示例显示了一个典型的测试场景中,使用JUnit木星和事务与@Sql测试:
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {final JdbcTemplate jdbcTemplate;@AutowiredTransactionalSqlScriptsTests(DataSource dataSource) {this.jdbcTemplate = new JdbcTemplate(dataSource);}@Test@Sql("/test-data.sql")void usersTest() {// verify state in test database:assertNumUsers(2);// execute code that uses the test data...}int countRowsInTable(String tableName) {return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);}void assertNumUsers(int expected) {assertEquals(expected, countRowsInTable("user"),"Number of rows in the [user] table.");}
}
注意,在运行usersTest()方法之后,不需要清理数据库,因为对数据库所做的任何更改(在测试方法内或在/test-data内)。由TransactionalTestExecutionListener自动回滚(有关详细信息,请参阅事务管理)。
使用@SqlMergeMode合并和覆盖配置
从Spring Framework 5.2开始,可以将方法级@Sql声明与类级声明合并。例如,这允许您为每个测试类提供一次数据库模式或一些公共测试数据的配置,然后为每个测试方法提供额外的、特定于用例的测试数据。要启用@Sql合并,请使用@SqlMergeMode(MERGE)注释您的测试类或测试方法。要禁用特定测试方法(或特定测试子类)的合并,可以通过@SqlMergeMode(OVERRIDE)切换回默认模式。有关示例和详细信息,请参阅@SqlMergeMode注释文档一节。
3.5.10 并行测试执行
当使用Spring TestContext框架时,Spring Framework 5.0引入了对在单个JVM中并行执行测试的基本支持。通常,这意味着大多数测试类或测试方法可以并行执行,而不需要对测试代码或配置进行任何更改。
注意:有关如何设置并行测试执行的详细信息,请参阅测试框架、构建工具或IDE的文档。
请记住,将并发性引入您的测试套件可能会导致意想不到的副作用、奇怪的运行时行为,以及间歇性地或看似随机地失败的测试。因此,Spring团队为何时不并行执行测试提供了以下一般指导原则。
如果下列测试:
- 使用Spring框架的@DirtiesContext支持。
- 使用Spring Boot的@MockBean或@SpyBean支持。
- 使用JUnit 4的@FixMethodOrder支持或任何设计来确保测试方法以特定顺序运行的测试框架特性。但是,请注意,如果整个测试类是并行执行的,那么这并不适用。
- 更改共享服务或系统(如数据库、消息代理、文件系统等)的状态。这既适用于嵌入式系统,也适用于外部系统。
注意:如果并行测试执行失败,并且出现异常,说明当前测试的ApplicationContext不再活动,这通常意味着ApplicationContext已从另一个线程的ContextCache中删除。
这可能是由于使用了@DirtiesContext或自动从ContextCache中清除。如果@DirtiesContext是罪魁祸首,那么您需要找到一种方法来避免使用@DirtiesContext或者从并行执行中排除这些测试。如果已经超过了ContextCache的最大大小,则可以增加缓存的最大大小。有关详细信息,请参阅关于上下文缓存的讨论。
警告:只有在底层TestContext实现提供了一个复制构造函数的情况下,才能在Spring TestContext框架中执行并行测试,正如TestContext的javadoc中所解释的那样。Spring中使用的DefaultTestContext提供了这样一个构造函数。但是,如果您使用提供自定义TestContext实现的第三方库,您需要验证它是否适合并行测试执行。
3.5.11 TestContext框架支持类
本节描述支持Spring TestContext框架的各种类。
Spring JUnit 4 Runner
Spring TestContext框架通过自定义运行器(支持JUnit 4.12或更高版本)提供了与JUnit 4的完整集成。通过注释测试类@RunWith (SpringJUnit4ClassRunner.class)或短@RunWith (SpringRunner.class)变体,开发人员可以实现标准的JUnit 4-based单元和集成测试,同时收获的好处和TestContext框架,比如支持加载应用程序上下文,依赖注入的测试实例,事务测试方法执行,等等。如果您希望将Spring TestContext框架与另一个运行器(如JUnit 4的参数化运行器)或第三方运行器(如MockitoJUnitRunner)一起使用,您可以选择使用Spring对JUnit规则的支持。
下面的代码清单显示了配置一个测试类来运行自定义Spring Runner的最低要求:
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {@Testpublic void testMethod() {// execute test logic...}
}
在前面的示例中,@ testexecutionlistener配置了一个空列表,以禁用默认监听器,否则将需要通过@ContextConfiguration配置ApplicationContext。
Spring JUnit 4规则
rules包提供了以下JUnit 4规则(在JUnit 4.12或更高版本上受支持):
- SpringClassRule
- SpringMethodRule
SpringClassRule是一个JUnit TestRule,它支持Spring TestContext框架的类级特性,而SpringMethodRule是一个JUnit MethodRule,它支持Spring TestContext框架的实例级和方法级特性。
与SpringRunner不同,Spring基于规则的JUnit支持的优点是独立于任何org.junit.runner.Runner实现,因此可以与现有的备选运行程序(如JUnit 4的参数化运行程序)或第三方运行程序(如MockitoJUnitRunner)结合使用。
为了支持TestContext框架的全部功能,您必须将SpringClassRule与SpringMethodRule结合起来。下面的例子展示了在集成测试中声明这些规则的正确方法:
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {@ClassRulepublic static final SpringClassRule springClassRule = new SpringClassRule();@Rulepublic final SpringMethodRule springMethodRule = new SpringMethodRule();@Testpublic void testMethod() {// execute test logic...}
}
JUnit 4 Support Classes
org.springframework.test.context。junit4包为基于JUnit 4的测试用例提供了以下支持类(在JUnit 4.12或更高版本上受支持):
- AbstractJUnit4SpringContextTests
- AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests是一个抽象的基本测试类,它集成了Spring TestContext框架和JUnit4环境中显式的ApplicationContext测试支持。当您扩展AbstractJUnit4SpringContextTests时,您可以访问一个受保护的applicationContext实例变量,您可以使用它来执行显式的bean查找或测试整个上下文的状态。
AbstractTransactionalJUnit4SpringContextTests是AbstractJUnit4SpringContextTests的抽象事务扩展,它为JDBC访问添加了一些方便的功能。这个类需要一个javax.sql。数据源bean和平台事务管理器bean将在ApplicationContext中定义。当您扩展AbstractTransactionalJUnit4SpringContextTests时,您可以访问一个受保护的jdbcTemplate实例变量,您可以使用它来运行SQL语句来查询数据库。您可以在运行与数据库相关的应用程序代码之前和之后使用这些查询来确认数据库状态,Spring确保这些查询在与应用程序代码相同的事务范围内运行。当与ORM工具一起使用时,一定要避免误报。正如在JDBC测试支持中提到的,AbstractTransactionalJUnit4SpringContextTests还提供了方便的方法,这些方法使用前面提到的jdbcTemplate委托给JdbcTestUtils中的方法。此外,AbstractTransactionalJUnit4SpringContextTests提供了一个executeSqlScript(..)方法,用于在配置的数据源上运行SQL脚本。
注意:这些类为扩展提供了方便。如果您不希望您的测试类被绑定到特定于Spring的类层次结构,您可以使用@RunWith(SpringRunner.class)或Spring的JUnit规则来配置您自己的自定义测试类。
SpringExtension for JUnit Jupiter
Spring TestContext框架提供了与JUnit Jupiter测试框架的完整集成,该框架是在JUnit 5中引入的。通过使用@ExtendWith(SpringExtension.class)注释测试类,您可以实现标准的基于JUnit类的单元和集成测试,同时获得TestContext框架的好处,例如支持加载应用程序上下文、测试实例的依赖注入、事务测试方法执行等等。
此外,由于JUnit Jupiter中丰富的扩展API, Spring在JUnit 4和TestNG支持的特性集之外还提供了以下特性:
- 测试构造函数、测试方法和测试生命周期回调方法的依赖项注入。有关更多细节,请参见SpringExtension的依赖项注入。
- 对基于SpEL表达式、环境变量、系统属性等的条件测试执行的强大支持。有关更多细节和示例,请参阅Spring JUnit Jupiter中@EnabledIf和@DisabledIf的文档,其中测试了注释。
- 自定义组成的注释,结合了来自Spring和JUnit Jupiter的注释。有关更多细节,请参见元注释支持中的@TransactionalDevTestConfig和@TransactionalIntegrationTest示例。
下面的代码清单展示了如何配置一个测试类,将SpringExtension与@ContextConfiguration结合使用:
// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {@Testvoid testMethod() {// execute test logic...}
}
由于您也可以使用JUnit 5中的注释作为元注释,Spring提供了@SpringJUnitConfig和@SpringJUnitWebConfig组成的注释,以简化测试ApplicationContext和JUnit Jupiter的配置。
下面的例子使用@SpringJUnitConfig来减少前面例子中使用的配置数量:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {@Testvoid testMethod() {// execute test logic...}
}
类似地,下面的例子使用@SpringJUnitWebConfig创建一个WebApplicationContext来使用JUnit Jupiter:
// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {@Testvoid testMethod() {// execute test logic...}
}
有关更多细节,请参见SpringJUnit Jupiter测试注释中的@SpringJUnitConfig和@SpringJUnitWebConfig的文档。
使用SpringExtension的依赖注入
SpringExtension实现了来自JUnit Jupiter的ParameterResolver扩展API,它让Spring为测试构造函数、测试方法和测试生命周期回调方法提供依赖注入。
具体地说,SpringExtension可以将来自测试的ApplicationContext的依赖注入到测试构造函数和方法中,这些构造函数和方法使用@BeforeAll、@AfterAll、@BeforeEach、@AfterEach、@Test、@RepeatedTest、@ParameterizedTest等进行注释。
构造函数注入
如果一个特定的参数在木星JUnit测试类的构造函数的类型是ApplicationContext(或者一个亚种)或与@ autowired注解或meta-annotated, @ qualifier,或@ value,春天注入特定参数的值与相应的测试的ApplicationContext bean或价值。
如果测试类构造函数被认为是自动可撤销的,那么Spring也可以配置为自动装配测试类构造函数的所有参数。如果满足下列条件之一(按优先顺序),则认为构造函数是自动可撤销的。
- 构造函数用@Autowired标注。
- @TestConstructor出现在测试类中,autowireMode属性设置为ALL。
- 默认的测试构造函数autowire模式已更改为ALL。
有关@TestConstructor的使用以及如何更改全局测试构造函数自动装配模式的详细信息,请参见@TestConstructor。
警告:如果测试类的构造函数被认为是自动可撤销的,那么Spring将负责解析构造函数中所有参数的参数。因此,注册到JUnit Jupiter的其他参数解析器无法解析这样一个构造函数的参数。
如果@DirtiesContext用于在测试方法之前或之后关闭测试的ApplicationContext,那么测试类的构造函数注入不能与JUnit Jupiter的@TestInstance(PER_CLASS)支持一起使用。
原因是@TestInstance(PER_CLASS)指示JUnit Jupiter在测试方法调用之间缓存测试实例。因此,测试实例将保留对最初从随后关闭的ApplicationContext注入的bean的引用。因为在这样的场景中,测试类的构造函数只会被调用一次,所以依赖项注入将不会再次发生,并且后续的测试将与来自关闭的ApplicationContext的bean进行交互,这可能会导致错误。
要将@DirtiesContext与“测试方法之前”或“测试方法之后”模式与@TestInstance(PER_CLASS)结合使用,必须配置Spring中的依赖项,通过字段或setter注入来提供,以便在测试方法调用之间重新注入。
在下面的示例中,Spring将从TestConfig.class加载的ApplicationContext中注入OrderService bean到OrderServiceIntegrationTests构造函数中。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {private final OrderService orderService;@AutowiredOrderServiceIntegrationTests(OrderService orderService) {this.orderService = orderService;}// tests that use the injected OrderService
}
请注意,该特性允许测试依赖关系是最终的,因此是不可变的。
如果spring.test.constructor.autowire。mode属性为all(参见@TestConstructor),我们可以省略前面示例中构造函数的@Autowired声明,结果如下。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {private final OrderService orderService;OrderServiceIntegrationTests(OrderService orderService) {this.orderService = orderService;}// tests that use the injected OrderService
}
方法注射
如果一个参数在木星JUnit测试方法或测试生命周期回调方法的类型是ApplicationContext(或者一个亚种)或与@ autowired注解或meta-annotated, @ qualifier,或@ value,春天注入特定参数的值与相应的测试的ApplicationContext bean。
在下面的例子中,Spring将从TestConfig.class中加载的ApplicationContext中的OrderService注入到deleteOrder()测试方法中:
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {@Testvoid deleteOrder(@Autowired OrderService orderService) {// use orderService from the test's ApplicationContext}
}
由于JUnit Jupiter中对参数解析器支持的健壮性,您还可以将多个依赖项注入到单个方法中,这些依赖项不仅来自Spring,还来自JUnit Jupiter本身或其他第三方扩展。
下面的例子展示了如何让Spring和JUnit Jupiter同时将依赖项注入placeorderrepeat()测试方法。
@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {@RepeatedTest(10)void placeOrderRepeatedly(RepetitionInfo repetitionInfo,@Autowired OrderService orderService) {// use orderService from the test's ApplicationContext// and repetitionInfo from JUnit Jupiter}
}
注意,使用来自JUnit Jupiter的@RepeatedTest可以让测试方法访问RepetitionInfo。
TestNG支持类
org.springframework.test.context。testng包为基于testng的测试用例提供了以下支持类:
AbstractTestNGSpringContextTests
AbstractTransactionalTestNGSpringContextTests
AbstractTestNGSpringContextTests是一个抽象的基本测试类,它在TestNG环境中集成了Spring TestContext框架和显式的ApplicationContext测试支持。当您扩展AbstractTestNGSpringContextTests时,您可以访问受保护的applicationContext实例变量,您可以使用它来执行显式的bean查找或测试整个上下文的状态。
AbstractTransactionalTestNGSpringContextTests是AbstractTestNGSpringContextTests的抽象事务扩展,它为JDBC访问添加了一些方便的功能。这个类需要一个javax.sql。数据源bean和平台事务管理器bean将在ApplicationContext中定义。在扩展AbstractTransactionalTestNGSpringContextTests时,可以访问受保护的jdbcTemplate实例变量,可以使用该变量执行SQL语句来查询数据库。您可以在运行与数据库相关的应用程序代码之前和之后使用这些查询来确认数据库状态,Spring确保这些查询在与应用程序代码相同的事务范围内运行。当与ORM工具一起使用时,一定要避免误报。正如在JDBC测试支持中提到的,AbstractTransactionalTestNGSpringContextTests还提供了方便的方法,这些方法使用前面提到的jdbcTemplate委托给JdbcTestUtils中的方法。此外,AbstractTransactionalTestNGSpringContextTests提供了一个executeSqlScript(..)方法,用于在配置的数据源上运行SQL脚本。
注意:这些类为扩展提供了方便。如果您不希望您的测试类绑定到一个spring特定类层次结构,您可以配置自己的自定义测试类通过使用@ContextConfiguration, @TestExecutionListeners等等,通过手动TestContextManager插装您的测试类。有关如何测试类的示例,请参阅AbstractTestNGSpringContextTests的源代码。
Spring 测试(第一部分)相关推荐
- (转)编写Spring的第一个案例并测试Spring的开发环境
http://blog.csdn.net/yerenyuan_pku/article/details/52832145 Spring4.2.5的开发环境搭建好了之后,我们来编写Spring的第一个案例 ...
- spring 测试 事务_Spring陷阱:事务测试被认为是有害的
spring 测试 事务 Spring杀手级功能之一是容器内集成测试 . 尽管EJB多年来一直缺乏此功能(Java EE 6终于解决了这个问题,但是我尚未进行测试),但是Spring从一开始就允许您从 ...
- Spring测试支持和上下文缓存
Spring为单元测试和集成测试提供了全面的支持-通过注释来加载Spring应用程序上下文,并与JUnit和TestNG等单元测试框架集成. 由于为每个测试加载大型应用程序上下文需要时间,因此Spri ...
- Spring框架第一天
## 今天课程:Spring框架第一天 ## ---------- **Spring框架的学习路线** 1. Spring第一天:Spring的IOC容器之XML的方式,Spring框架与Web项目整 ...
- 使用Eclipse开发Spring的第一个简单程序
使用Eclipse开发Spring的第一个简单程序 本篇文章将通过一个简单的入门程序向读者演示Spring框架的使用过程,具体如下: 使用Eclipse创建Web应用并导入JAR包 使用Eclipse ...
- 解决Spring测试出现@EnableAsync annotation metadata was not injected
Spring测试的时候,出现如下报错,非常奇怪 Caused by: org.springframework.beans.factory.BeanCreationException: Error c ...
- 视频教程-①Spring Cloud 第一季(初级入门篇)-Java
①Spring Cloud 第一季(初级入门篇) 2011年毕业后在澳门 遊澳集团(UO Group)旗下某IT科技公司从事 android,php,j2ee开发工作,负责 国际短信发送系统.银联支付 ...
- Spring依赖注入IOC(给字段赋值)和Spring测试
含义: IOC是一种思想,它的一个重点是在系统运行中,动态的向某个对象提供它所需要的其他对象.这一点是通过DI(Dependency Injection,依赖注入)来实现的:Spring中对象的属性字 ...
- 万里长征之测试第一步
测试第一步 什莫是需求 用户需求与软件需求 软件开发的过程 什莫是BUG? 什莫是测试用例? 开发模型(瀑布模型,螺旋模型,增量模型,迭代模型) 测试模型(V模型,W模型) 什莫是需求 1.什么是需求 ...
最新文章
- Unix环境高级编程学习笔记(一)
- ZZ:Business Analysis Career Path
- mysql 备份库的shell_MySQL数据库的shell脚本自动备份
- vs2008 使用Visual Leak Detector检测内存泄漏
- 探索ASP.NET MVC5系列之~~~6.Session篇(进程外Session)
- java reader类 实例_Java Reader ready()用法及代码示例
- 推荐5款好用的安卓版RSS应用
- python嵌套html开发gui_如何在Python Tkinter GUI中嵌入Cartopy?
- linux 脚本1加到100,shell脚本之从1加到100之和的思路
- eclipse错误及解决方法
- 使用Json.Net处理json序列化和反序列化接口或继承类
- xenserver 虚拟机扩容lvm磁盘分区的方法_vm虚拟机中linux Centos7.4硬盘扩容
- oracle保留两位小数解决方案
- python函数长度单位换算,(最新整理)长度单位换算
- ORACLE 19C 单实例数据库安装
- 2021秋软工实践个人作业一
- 前端-JS基础之运算符
- Adversarial Learning
- 谷歌的招聘(pta)
- php开源电影,迅睿PHP开源视频电影CMS系统 v1.1.0