Spring 5 中文解析之测试篇-集成测试(上)

时间:2022-11-24 20:59:21

本节(本章其余部分)涵盖了Spring应用程序的集成测试。它包括以下主题:

3.1 概要

能够执行一些集成测试而无需部署到应用程序服务器或连接到其他企业基础结构,这一点很重要。这样可以测试以下内容:

  • 正确连接Spring IoC容器上下文。
  • 使用JDBC或ORM工具进行数据访问。这可以包括诸如SQL语句的正确性、Hibernate查询、JPA实体映射之类的东西。

Spring框架为Spring测试模块中的集成测试提供了一流的支持。实际的JAR文件的名称可能包括发行版,也可能采用长​​org.springframework.test​​​格式,具体取决于你从何处获取(请参阅“​​依赖管理​​​”部分中的说明)。该库包含​​org.springframework.test​​包,其中包含用于与Spring容器进行集成测试的有价值的类。此测试不依赖于应用程序服务器或其他部署环境。此类测试的运行速度比单元测试慢,但比依赖于部署到应用程序服务器的等效Selenium测试或远程测试快。

单元和集成测试支持以注解驱动的​​Spring TestContext 框架​​​的形式提供。​​TestContext​​框架与实际使用的测试框架无关,该框架允许各种环境(包括JUnit,TestNG和其他环境)中对测试进行检测。

3.2 集成测试目标

Spring的集成测试支持的主要目标如下:

接下来的几节描述了每个目标,并提供了有关实现和配制详细信息的链接。

Fixture意思:JUnit提供了编写测试前准备、测试后清理的固定代码,我们称之为Fixture

3.2.1 上下文管理和缓存

Spring ​​TestContext​​​ 框架提供了Spring ​​ApplicationContext​​​实例和​​WebApplicationContext​​​实例的一致加载以及这些上下文的缓存。支持加载上下文的缓存很重要,因为启动时间可能会成为一个问题-不是因为Spring本身的开销,而是因为Spring容器实例化的对象需要时间才能实例化。例如,具有50到100个​​Hibernate​​映射文件的项目可能需要10到20秒来加载映射文件,并且在每个测试fixture中运行每个测试之前要承担该消耗,这会导致整体测试运行速度变慢,从而降低了开发人员的工作效率。

测试类通常声明XML或Groovy配置元数据的资源位置数组(通常是在类路径中)或用于配置应用程序的组件类的数组。这些位置或类与​​web.xml​​或其他用于生产部署的配置文件中指定的位置或类相同或相似。

默认情况下,加载后,已配置的​​ApplicationContext​​​将重新用每个测试。因此,每个测试套件仅产生一次安装成本,并且随后的测试执行要快得多。在这种情况下,术语“测试套件”是指所有测试都在相同JVM中运行,例如,所有测试都从给定项目或模块的​​Ant​​​、​​Maven​​​或​​Gradle​​​构建运行。在不太可能的情况下,测试破坏了应用程序上下文并需要重新加载(例如,通过修改bean定义或应用程序对象的状态),可以将​​TestContext​​框架配置为重新加载配置并重建应用程序上下文,然后再执行下一个测试。

请参见使用TestContext框架进行​​上下文管理​​​和​​上下文缓存​​。

3.2.2 测试装置的依赖注入

当​​TestContext​​​框架加载你的应用程序上下文时,可以选择地用依赖注入来配置测试类的实例。这提供了一种方便的机制,可以通过在应用程序上下文中使用预配置的bean来设置测试​​fixture​​​。这里一个强大的好处是,你可以跨各种测试场景重用应用程序上下文(例如,用于配置spring管理的对象图、事务代理、数据源实例等),从而避免为单个测试用例重复复杂的测试​​fixture​​设置。

例如,考虑一个场景,其中我们有一个类(​​HibernateTitleRepository​​),该类为Title域实体实现数据访问逻辑。我们要编写集成测试来测试以下方面:

  • Spring配置:基本上,与​​HibernateTitleRepository​​ bean的配置有关的一切都正确并存在吗?
  • ​Hibernate​​映射文件配置:是否已正确映射所有内容,并且是否有正确的延迟加载配置?
  • ​HibernateTitleRepository​​的逻辑:此类的配置实例是否按预期执行?

请参见使用​​TestContext框架​​​进行测试​​fixture​​的依赖注入。

3.2.3 事物管理

访问真实数据库的测试中的一个常见问题是它们对持久存储状态的影响。即使使用开发数据库,对状态的更改也可能会影响以后的测试。同样,许多操作(例如插入或修改持久数据)无法在事物之外执行(或验证)。

​TestContext​​​框架解决了这个问题。默认情况下,框架为每个测试创建并回滚事务。你可以编写可以假定存在事务的代码。如果在测试中调用事务代理对象,则对象将根据其配置事务语义正确运行。此外,如果测试方法在测试管理的事务中运行时删除了选定表的内容,则该事务将默认回滚,并且数据库将返回到执行测试之前的状态。通过使用在测试的应用程序上下文中定义的​​PlatformTransactionManager​​ bean,可以为测试提供事务支持。

如果你想要提交一个事务(不常见,但是当你想要一个特定的测试填充或修改数据库时,偶尔会有用),你可以通过使用​​@Commit​​​注解告诉​​TestContext​​框架使事务提交而不是回滚。

请参阅使用​​TestContext框架​​进行事务管理。

3.2.4 集成测试支持的类

Spring ​​TestContext​​框架提供了几个抽象支持类,这些基础测试类为测试框架提供了定义明确的钩子,以方便的实例变量和方法,可用于访问以下内容:

  • ApplicationContext,用于执行显式的bean查找或测试整个上下文的状态。
  • 一个JdbcTemplate,用于执行SQL语句来查询数据库。你可以使用此类查询在执行与数据库相关的应用程序代码之前和之后确认数据库状态,并且Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具一起使用时,请确保避免​​误报​​。

另外,你可能希望使用针对你的项目的实例变量和方法构建自己的自定义,应用程序范围的超类。

请参阅​​TestContext框架​​的支持类。

3.3 JDBC测试支持

​org.springframework.test.jdbc​​​包包含​​JdbcTestUtils​​​,它是JDBC相关实用程序功能的集合,旨在简化标准数据库测试方案。具体来说,​​JdbcTestUtils​​提供以下静态实用程序方法。

  • ​countRowsInTable(..)​​:计算给定表中的行数。
  • ​countRowsInTableWhere(..)​​​:使用提供的​​WHERE​​子句计算给定表中的行数。
  • ​deleteFromTables(..)​​:删除指定表中的所有行。
  • ​deleteFromTableWhere(..)​​​: 使用提供的​​WHERE​​子句从给定表中删除行。

​AbstractTransactionalJUnit4SpringContextTests​​​和​​AbstractTransactionalTestNGSpringContextTests​​​提供了便利的方法,这些方法委托给​​JdbcTestUtils​​​中的上述方法。​​spring-jdbc​​​模块提供了对配置和启动嵌入式数据库的支持你可以在与数据库交互的集成测试中使用它。有关详细信息,请参见​​嵌入式数据库支持​​​和使用​​嵌入式数据库测试数据​​访问逻辑。

3.4 注解

本节介绍了在测试Spring应用程序时可以使用的注解。它包括以下主题:

3.4.1 Spring测试注解

Spring框架提供了以下特定于Spring的注解集,你可以在单元测试和集成测试中将它们与​​TestContext​​框架结合使用。有关更多信息,请参见相应的javadoc,包括默认属性值、属性别名和其他详细信息。

Spring的测试注解包括以下内容:

@BootstrapWith

​@BootstrapWith​​​是一个类级别的注解,可用于配置如何引导Spring ​​TestContext​​​ 框架。具体来说,你可以使用​​@BootstrapWith​​​指定自定义​​TestContextBootstrapper​​​。有关更多详细信息,请参见有关​​引导TestContext框架​​的部分。

参考代码:​​org.liyong.test.annotation.test.spring.ConfigClassApplicationContextTests​

@ContextConfiguration

​@ContextConfiguration​​​定义了用于确定如何为集成测试加载和配置​​ApplicationContext​​​的类级元数据。具体来说,​​@ContextConfiguration​​声明应用程序上下文资源位置或用于加载上下文的组件类。

资源位置通常是位于类路径中的XML配置文件或Groovy脚本,而组件类通常是​​@Configuration​​​类。但是,资源位置也可以引用文件系统中的文件和脚本,组件类可以是​​@Component​​​类、​​@Service​​​类等等。有关更多详细信息,请参见​​组件类​​。

以下示例显示了一个指向XML文件的​​@ContextConfiguration​​注解:

@ContextConfiguration("/test-config.xml") //
class XmlApplicationContextTests {
// class body...
}
  1. 引用XML文件。

以下示例显示了一个​​@ContextConfiguration​​注解,该注解引用了一个类:

@ContextConfiguration(classes = TestConfig.class) //1
class ConfigClassApplicationContextTests {
// class body...
}
  1. 引用类文件

参考代码:​​org.liyong.test.annotation.test.spring.ConfigClassApplicationContextTests​

作为声明资源位置或组件类的替代方法或补充,可以使用​​@ContextConfiguration​​​声明​​ApplicationContextInitializer​​类。以下示例显示了这种情况:

@ContextConfiguration(initializers = CustomContextIntializer.class) 
class ContextInitializerTests {
// class body...
}

参考代码:​​org.liyong.test.annotation.test.spring.ContextInitializerTests​

你可以选择使用​​@ContextConfiguration​​​来声明​​ContextLoader​​策略。但是,你通常不需要显式配置加载器,因为默认加载器支持初始化程序以及资源位置或组件类。

以下示例同时使用配置位置和加载器:

@ContextConfiguration(locations = "/test-context.xml", loader = CustomContextLoader.class) //1
class CustomLoaderXmlApplicationContextTests {
// class body...
}
  1. 配置位置和自定义加载器。

​@ContextConfiguration​​为继承资源位置或配置类以及超类声明上下文初始化器提供支持。

有关更多详细信息,请参见​​上下文管理​​​和​​@ContextConfiguration​​ javadocs。

参考代码:​​org.liyong.test.annotation.test.spring.CustomLoaderXmlApplicationContextTests​

@WebAppConfiguration

​@WebAppConfiguration​​​是一个类级别的注解,可用于声明为集成测试加载的​​ApplicationContext​​​应该是​​WebApplicationContext​​​。​​@WebAppConfiguration​​​仅存在于测试类上,可以确保为测试加​​@WebApplicationContext​​​,并使用默认值​​file:src/main/webapp​​​作为Web应用程序根目录(也就是即资源基本路径)。资源基础路径用于在后台创建​​MockServletContext​​​,该​​MockServletContext​​​用作测试的​​WebApplicationContext​​​的​​ServletContext​​。

以下示例显示了如何使用​​@WebAppConfiguration​​注解:

@ContextConfiguration
@WebAppConfiguration //
class WebAppTests {
// class body...
}

要覆盖默认值,可以使用隐式值属性指定其他基础资源路径。​​classpath:​​​和​​file:​​资源前缀均受支持。如果未提供资源前缀,则假定该路径是文件系统资源。以下示例显示如何指定类路径资源:

@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources") //1
class WebAppTests {
// class body...
}
  1. 指定类路径资源。

注意,​​@WebAppConfiguration​​​必须与​​@ContextConfiguration​​一起使用,可以在单个测试类中使用,也可以在测试类层次结构中使用。

有关更多详细信息,请参见​​@WebAppConfiguration​​ javadoc。

参考代码:​​org.liyong.test.annotation.test.spring.WebAppTests​

@ContextHierarchy

​@ContextHierarchy​​​是一个类级注解,用于定义集成测试的​​ApplicationContext​​​实例的层次结构。 ​​@ContextHierarchy​​​应该用一个或多个​​@ContextConfiguration​​​实例的列表声明,每个实例定义上下文层次结构中的一个级别。以下示例演示了在单个测试类中使用​​@ContextHierarchy​​​(也可以在测试类层次结构中使用​​@ContextHierarchy​​):

@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class ContextHierarchyTests {
// class body...
}

参考代码:​​org.liyong.test.annotation.test.spring.ContextHierarchyTests​

@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
class WebIntegrationTests {
// class body...
}

参考代码:​​org.liyong.test.annotation.test.spring.WebIntegrationTests​

如果需要合并或覆盖测试类层次结构中上下文层次结构的给定级别的配置,则必须通过在类层次结构的每个对应级别上为​​@ContextConfiguration​​​中的name属性提供相同的值来显式地命名该级别。有关更多示例,请参见​​上下文层​​​次结构和​​@ContextHierarchy​​ javadoc。

@ActiveProfiles

​@ActiveProfiles​​​是一个类级别的注解,用于声明在为集成测时加载​​ApplicationContext​​时应启用哪些bean定义配置文件。

以下示例表明​​dev​​配置文件应处于活动状态:

@ContextConfiguration
@ActiveProfiles("dev") //1
class DeveloperTests {
// class body...
}
  1. 指示开发配置文件应处于活动状态。

以下示例表明​​dev​​​和​​integration​​配置文件均应处于活动状态:

@ContextConfiguration
@ActiveProfiles({"dev", "integration"}) //1
class DeveloperIntegrationTests {
// class body...
}
  1. 指示​​dev​​​和​​integration​​配置文件应该处于活动状态。

​@ActiveProfiles​​​提供了对继承默认情况下超类声明的活动bean定义配置文件的支持。你还可以通过实现自定义​​ActiveProfilesResolver​​​并使用​​@ActiveProfiles​​​的​​resolver​​属性对其进行注册,以编程方式解析活动bean定义配置文件。

参见​​环境配置文件和@ActiveProfiles javadoc的上下文配置​​以获得示例和更多细节。

参考代码:​​org.liyong.test.annotation.test.spring.DeveloperIntegrationTests​

@TestPropertySource

​@TestPropertySource​​​是类级别的注解,可用于配置属性文件和内联属性的位置,这些属性和内联属性将被添加到环境中针对集成测试加载的​​ApplicationContext​​​的​​PropertySources​​集中。

下面的示例演示如何从类路径声明属性文件:

@ContextConfiguration
@TestPropertySource("/test.properties") //1
class MyIntegrationTests {
// class body...
}
  1. 从类路径根目录中的test.properties获取属性。

下面的示例演示如何声明内联属性:

@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" }) //1
class MyIntegrationTests {
// class body...
}
  1. 声明时区和端口属性。

有关示例和更多详细信息,请参见使用测试​​属性源进行上下文配置​​。

@DynamicPropertySource

​@DynamicPropertySource​​​是方法级别的注解,可用于注册动态属性,以将动态属性添加到环境中针对集成测试加载的​​ApplicationContext​​​的​​PropertySources​​​集中。当你不预先知道属性的值时,例如,如果属性是由外部资源管理的,例如由​​Testcontainers​​项目管理的容器,则动态属性很有用。

下面的示例演示如何注册动态属性:

@ContextConfiguration
class MyIntegrationTests {

static MyExternalServer server = // ...

@DynamicPropertySource //1
static void dynamicProperties(DynamicPropertyRegistry registry) { //2
registry.add("server.port", server::getPort); //3
}

// tests ...
}
  1. 使用​​@DynamicPropertySource​​注解静态方法。
  2. 接受​​DynamicPropertyRegistry​​作为参数。
  3. 注册要从服务器延迟检索的动态​​server.port​​属性。

有关更多详细信息,请参见使用​​动态属性源进行上下文配置​​。

@DirtiesContext

​@DirtiesContext​​​表示底层的Spring ​​ApplicationContext​​在执行测试期间已被清理(即,该测试以某种方式修改或破坏了它(例如,通过更改单例bean的状态)),应将其关闭。当应用程序上下文被标记为清理时,它将从测试框架的缓存中删除并关闭。因此,对于需要具有相同配置元数据的上下文的后续测试,将重新构建底层Spring容器。

你可以将​​@DirtiesContext​​​用作同一类或类层次结构中的类级别和方法级别的注解。在这种情况下,取决于配置的​​methodMode​​​和​​classMode​​​,在任何此类带注解的方法之前或之后以及当前测试类之前或之后,​​ApplicationContext​​均标记为清理。

以下示例说明了在各种配置情况下何时清理上下文:

  • 在当前测试类之前,在类模式设置为​​BEFORE_CLASS​​的类上声明时。
@DirtiesContext(classMode = BEFORE_CLASS) //1
class FreshContextTests {
// some tests that require a new Spring container
}
  1. 在当前测试类之前清理上下文。
  • 在当前测试类之后,当在类模式设置为​​AFTER_CLASS​​(即默认类模式)的类上声明时。
@DirtiesContext 
class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  1. 当前测试类之后清理上下文。
  • 在当前测试类中的每个测试方法之后,在类模式设置为​​AFTER_EACH_TEST_METHOD​​的类上声明时。
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD) 
class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
  1. 每种测试方法后清理上下文。
  • 在当前测试之前,当在方法模式设置为​​BEFORE_METHOD​​的方法上声明时。
@DirtiesContext(methodMode = BEFORE_METHOD) //1
@Test
void testProcessWhichRequiresFreshAppCtx() {
// some logic that requires a new Spring container
}
  1. 在当前测试方法之情了上下文。
  • 当前测试之后,当在方法模式设置为​​AFTER_METHOD​​的方法上声明时(即默认方法模式)。
@DirtiesContext //1
@Test
void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}
  1. 当前测试方法后清理上下文。

如果在使用​​@ContextHierarchy​​​将上下文配置为上下文层次结构的一部分的测试中使用​​@DirtiesContext​​​,则可以使用​​hierarchyMode​​​标志控制清除上下文缓存的方式。默认情况下,使用穷举算法清除上下文缓存,不仅包括当前级别,还包括共享当前测试共有的祖先上下文的所有其他上下文层次结构。驻留在公共祖先上下文的子层次结构中的所有​​ApplicationContext​​实例都将从上下文缓存中删除并关闭。如果穷举算法对于特定用例来说过于强大,那么你可以指定更简单的当前级别算法,如下面的示例所示。

@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
class BaseTests {
// class body...
}

class ExtendedTests extends BaseTests {

@Test
@DirtiesContext(hierarchyMode = CURRENT_LEVEL) //1
void test() {
// some logic that results in the child context being dirtied
}
}
  1. 使用当前级别的算法。

有关​​EXHAUSTIVE​​​和​​CURRENT_LEVEL​​​算法的更多详细信息,请参见​​DirtiesContext.HierarchyMode​​ javadoc。

@TestExecutionListeners

​@TestExecutionListeners​​​定义了用于配置应在​​TestContextManager​​​中注册的​​TestExecutionListener​​​实现的类级元数据。通常,​​@TestExecutionListeners​​​与​​@ContextConfiguration​​结合使用。

下面的示例演示如何注册两个​​TestExecutionListener​​实现:

@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class, AnotherTestExecutionListener.class}) //1
class CustomTestExecutionListenerTests {
// class body...
}
  1. 注册两个​​TestExecutionListener​​实现。

默认情况下,​​@TestExecutionListeners​​​支持继承的监听器。有关示例和更多详细信息,请参见​​javadoc​​。

@Commit

​@Commit​​​表示应在测试方法完成后提交用于事务性测试方法的事务。你可以将​​@Commit​​​用作​​@Rollback(false)​​​的直接替代品,以更明确地传达代码的意图。与​​@Rollback​​​类似,​​@ Commit​​也可以声明为类级别或方法级别的注解。

以下示例显示了如何使用​​@Commit​​注解:

@Commit //1
@Test
void testProcessWithoutRollback() {
// ...
}
  1. 将测试结果提交到数据库。

@Rollback

​@Rollback​​​表示在测试方法完成后是否应回退用于事务性测试方法的事务。如果为true,则回滚该事务。否则,将提交事务(另请参见​​@Commit​​​)。即使未明确声明​​@Rollback​​​,Spring ​​TestContext​​框架中用于集成测试的回滚默认为true。

当声明为类级注解时,​​@Rollback​​​定义测试类层次结构中所有测试方法的默认回滚语义。当声明为方法级别的注解时,​​@Rollback​​​定义特定测试方法的回滚语义,从而可能覆盖类级别的​​@Rollback​​​或​​@Commit​​语义。

以下示例使测试方法的结果不回滚(即,结果已提交到数据库):

@Rollback(false) //1
@Test
void testProcessWithoutRollback() {
// ...
}
  1. 不要回滚结果。

@BeforeTransaction

​@BeforeTransaction​​​表示,对于已配置为使用Spring的​​@Transactional​​​注解在事务内运行的测试方法,带注解的void方法应在事务开始之前运行。​​@BeforeTransaction​​方法不需要public访问限定,可以在基于Java 8的接口默认方法。

以下示例显示了如何使用​​@BeforeTransaction​​注解:

@BeforeTransaction //1
void beforeTransaction() {
// logic to be executed before a transaction is started
}
  1. 在事务之前运行此方法。

@AfterTransaction

​@AfterTransaction​​​表示,对于已配置为通过使用Spring的​​@Transactional​​​注解在事务内运行的测试方法,带注解的void方法应在事务结束后运行。​​@AfterTransaction​​方法不需要public访问限定,可以在基于Java 8的接口默认方法中声明。

@AfterTransaction //1
void afterTransaction() {
// logic to be executed after a transaction has ended
}
  1. 事务后运行此方法。

@Sql

​@Sql​​用于注解测试类或测试方法,以配置在集成测试期间针对给定数据库运行的SQL脚本。以下示例显示了如何使用它:

@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})//1
void userTest() {
// execute code that relies on the test schema and test data
}
  1. 运行此测试的两个脚本。

有关更多详细信息,请参见​​使用@Sql声明式执行SQL脚本​​。

@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
}
  1. 在SQL脚本中设置注释前缀和分隔符。

@SqlMergeMode

​@SqlMergeMode​​​用于注释测试类或测试方法,以配置是否将方法级​​@Sql​​​声明与类级​​@Sql​​​声明合并。如果未在测试类或测试方法上声明​​@SqlMergeMode​​​,则默认情况下将使用​​OVERRIDE​​​合并模式。在​​OVERRIDE​​​模式下,方法级别的​​@Sql​​​声明将有效地覆盖类级别的​​@Sql​​声明。

请注意,方法级别的​​@SqlMergeMode​​声明将覆盖类级别的声明。

下面的示例演示如何在类级别使用​​@SqlMergeMode​​。

@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
@SqlMergeMode(MERGE) //1
class UserTests {

@Test
@Sql("/user-test-data-001.sql")
void standardUserProfile() {
// execute code that relies on test data set 001
}
}
  1. 对于类中的所有测试方法,将​​@Sql​​​合并模式设置为​​MERGE​​。

下面的示例演示如何在方法级别使用​​@SqlMergeMode​​。

@SpringJUnitConfig(TestConfig.class)
@Sql("/test-schema.sql")
class UserTests {

@Test
@Sql("/user-test-data-001.sql")
@SqlMergeMode(MERGE) //1
void standardUserProfile() {
// execute code that relies on test data set 001
}
}

对于特定的测试方法,将​​@Sql​​​合并模式设置为​​MERGE​​。

@SqlGroup

​@SqlGroup​​​是一个容器注解,它聚合了多个​​@Sql​​​注解。你可以本地使用​​@SqlGroup​​​声明多个嵌套的​​@Sql​​​注解,也可以将其与Java 8对可重复注解的支持结合使用,其中​​@Sql​​可以在同一类或方法上多次声明,从而隐式生成此容器注解。下面的示例显示如何声明一个SQL组:

@Test
@SqlGroup({ //1
@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
}
  1. 声明一组SQL脚本。
3.4.2 标准注解支持

Spring ​​TestContext​​ 框架的所有配置的标准语义都支持以下注解。请注意,这些注解并非特定于测试,可以在Spring 框架中的任何地方使用。

  • ​@Autowired​
  • ​@Qualifier​
  • ​@Value​
  • ​@Resource​​​ (​​javax.annotation​​) 如果支持JSR-250
  • ​@ManagedBean​​​ (​​javax.annotation​​) 如果支持 JSR-250
  • ​@Inject​​​ (​​javax.inject​​) 如果支持 JSR-330
  • ​@Named​​​ (​​javax.inject​​) 如果支持 JSR-330
  • ​@PersistenceContext​​​ (​​javax.persistence​​) 如果支持JPA
  • ​@PersistenceUnit​​​ ​​(javax.persistence​​) 如果支持JPA
  • ​@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 JUnit4测试注解

以下注解仅在与SpringRunner、Spring的JUnit 4规则或Spring的JUnit 4支持类一起使用时才受支持:

@IfProfileValue

​@IfProfileValue​​​表示已为特定测试环境启用带注解的测试。如果配置的​​ProfileValueSource​​返回提供的名称的匹配值,则使用测试。否则,测试将被禁用,并且实际上将被忽略。

你可以在类级别、方法级别或两者上应用​​@IfProfileValue​​​。对于该类或其子类中的任何方法,​​@IfProfileValue​​​的类级别用法优先于方法级别用法。具体来说,如果在类级别和方法级别都启用了测试,则启用该测试。缺少​​@IfProfileValue​​​意味着隐式启用了测试。这类似于JUnit 4的​​@Ignore​​​注解的语义,不同之处在于​​@Ignore​​的存在始终会禁用测试。

以下示例显示了具有​​@IfProfileValue​​注解的测试:

@IfProfileValue(name="java.vendor", value="Oracle Corporation") //1
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
  1. 仅当Java供应商是“ Oracle Corporation”时才运行此测试。

另外,你可以为​​@IfProfileValue​​​配置值列表(具有​​OR​​语义)以在JUnit 4环境中实现类似于TestNG的测试组支持。考虑以下示例:

@IfProfileValue(name="test-groups", values={"unit-tests", "integration-tests"}) //1
@Test
public void testProcessWhichRunsForUnitOrIntegrationTestGroups() {
// some logic that should run only for unit and integration test groups
}
  1. 对单元测试和集成测试运行此测试。

@ProfileValueSourceConfiguration

​@ProfileValueSourceConfiguration​​​是一个类级别的注解,它指定检索通过​​@IfProfileValue​​​注解配置的配置文件值时要使用哪种​​ProfileValueSource​​​类型。如果未为测试声明​​@ProfileValueSourceConfiguration​​​,则默认使用​​SystemProfileValueSource​​​。以下示例显示了如何使用​​@ProfileValueSourceConfiguration​​:

@ProfileValueSourceConfiguration(CustomProfileValueSource.class) //1
public class CustomProfileValueSourceTests {
// class body...
}
  1. 使用自定义配置文件值源。

参考代码:​​org.liyong.test.annotation.test.spring.ProfileValueTest​

@Timed

​@Timed​​表示带注解的测试方法必须在指定的时间段(以毫秒为单位)内完成执行。如果单元测试片段执行时间超过指定的时间段,则测试将失败。

该时间段包括运行测试方法本身,测试的任何重复(请参见​​@Repeat​​)以及测试套件的任何设置或拆除。以下示例显示了如何使用它:

@Timed(millis = 1000)//1
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to execute
}
  1. 将测试时间设置为一秒。

Spring的​​@Timed​​​注解与JUnit 4的​​@Test(timeout = ...)​​​支持具有不同的语义。具体来说,由于JUnit 4处理测试执行超时的方式(即通过在单独的线程中执行测试方法),如果测试花费的时间太长,​​@Test(timeout = ...)​​​会抢先通过测试。另一方面,Spring的​​@Timed​​不会抢先通过测试,而是在失败之前等待测试完成。

@Repeat

​@Repeat​​​表示必须重复运行带注解的测试方法。注解中指定了要执行测试方法的次数。重复执行的范围包括测试方法本身的执行以及测试套件中任何安装或拆除。以下示例显示了如何使用​​@Repeat​​注解:

@Repeat(10) //1
@Test
public void testProcessRepeatedly() {
// ...
}
  1. 重复此测试十次。
3.4.4 Spring JUnit Jupiter测试注解

以下注解仅在与​​SpringExtension​​​和​​JUnit Jupiter​​(即JUnit 5中的编程模型)结合使用时才受支持:

@SpringJUnitConfig

​@SpringJUnitConfig​​​是一个组合注解,它将​​JUnit Jupiter​​​中的​​@ExtendWith(SpringExtension.class)​​​与Spring ​​TestContext​​​ 框架中的​​@ContextConfiguration​​​组合在一起。它可以在类级别用作​​@ContextConfiguration​​​的直接替代。关于配置选项,​​@ContextConfiguration​​​和​​@SpringJUnitConfig​​​之间的唯一区别是可以使用​​@SpringJUnitConfig​​中的value属性声明组件类。

以下示例显示如何使用​​@SpringJUnitConfig​​注解指定配置类:

@SpringJUnitConfig(TestConfig.class) //1
class ConfigurationClassJUnitJupiterSpringTests {
// class body...
}
  1. 指定配置类。

以下示例显示如何使用​​@SpringJUnitConfig​​注解指定配置文件的位置:

@SpringJUnitConfig(locations = "/test-config.xml") //1
class XmlJUnitJupiterSpringTests {
// class body...
}
  1. 指定配置文件的位置。

有关更多详细信息,请参见​​上下文管理​​​以及​​@SpringJUnitConfig​​​和​​@ContextConfiguration​​的javadoc。

@SpringJUnitWebConfig

​@SpringJUnitWebConfig​​​是一个组合的注解,它将来自​​JUnit Jupiter​​​的​​@ExtendWith(SpringExtension.class)​​​与来自Spring ​​TestContext​​​ 框架的​​@ContextConfiguration​​​和​​@WebAppConfiguration​​​组合在一起。你可以在类级别使用它作为​​@ContextConfiguration​​​和​​@WebAppConfiguration​​​的直接替代。关于配置选项,​​@ ContextConfiguration​​​和​​@SpringJUnitWebConfig​​​之间的唯一区别是可以使用​​@SpringJUnitWebConfig​​​中的value属性来声明组件类。另外,只能使用​​@SpringJUnitWebConfig​​​中的​​resourcePath​​​属性来覆盖​​@WebAppConfiguration​​​中的​​value​​属性。

以下示例显示如何使用​​@SpringJUnitWebConfig​​注解指定配置类:

@SpringJUnitWebConfig(TestConfig.class) //1
class ConfigurationClassJUnitJupiterSpringWebTests {
// class body...
}
  1. 指定配置类。

以下示例显示如何使用@SpringJUnitWebConfig注解指定配置文件的位置:

@SpringJUnitWebConfig(locations = "/test-config.xml") //1
class XmlJUnitJupiterSpringWebTests {
// class body...
}
  1. 指定配置文件的位置。

有关更多详细信息,请参见​​上下文管理​​​以及​​@SpringJUnitWebConfig​​​,​​@ContextConfiguration​​​和​​@WebAppConfiguration​​的javadoc。

参考代码:​​org.liyong.test.annotation.test.spring.ConfigurationClassJUnitJupiterSpringWebTests​

@TestConstructor

​@TestConstructor​​​是类型级别的注解,用于配置如何从测试的​​ApplicationContext​​中的组件自动连接测试类构造函数的参数。

如果在测试类上不存在​​@TestConstructor​​​或​​meta-present​​​,则将使用默认的测试构造函数自动装配模式。有关如何更改默认模式的详细信息,请参见下面的提示。但是请注意,构造函数上的​​@Autowired​​​本地声明优先于​​@TestConstructor​​和默认模式。

更改默认的测试构造函数自动装配模式

可以通过将JVM系统属性​​spring.test.constructor.autowire.mode​​​设置为all来更改默认的测试构造函数自动装配模式。或者,可以通过​​SpringProperties​​机制更改默认模式。

如果未设置​​spring.test.constructor.autowire.mode​​属性,则不会自动装配测试类构造函数。

从Spring框架5.2开始,仅将​​@TestConstructor​​​与​​SpringExtension​​​结合使用以与JUnit Jupiter一起使用。请注意,​​SpringExtension​​​通常会自动为你注册-例如,在使用​​@SpringJUnitConfig​​​和​​@SpringJUnitWebConfig​​之类的注解或Spring Boot Test中与测试相关的各种注解时。

@EnabledIf

​@EnabledIf​​​用于表示已注解的JUnit Jupiter测试类或测试方法启用,如果提供的表达式的值为​​true​​​,则应运行​​@EnabledIf​​​。具体来说,如果表达式的计算结果为​​Boolean.TRUE​​​或等于​​true​​的字符串(忽略大小写),则启用测试。在类级别应用时,默认情况下也会自动启用该类中的所有测试方法。

表达式可以是以下任意一种:

  • Spring表达式语言。例如:​​@EnabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")​
  • Spring ​​Environment​​​中可用属性的占位符。例如:​​@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​​​,则不应执行该操作。具体来说,如果表达式的计算结果为​​Boolean.TRUE​​或等于true的字符串(忽略大小写),则测试将被禁用。当在类级别应用时,该类中的所有测试方法也会自动禁止。

表达式可以是以下任意一种:

  • Spring表达式语言。例如:​​@DisabledIf("#{systemProperties['os.name'].toLowerCase().contains('mac')}")​
  • Spring Environment中可用属性的占位符。例如:​​@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​(仅支持 JUnit 4)
  • ​@Timed​(仅支持 JUnit 4)
  • ​@IfProfileValue​(仅支持 JUnit 4)
  • ​@ProfileValueSourceConfiguration​(仅支持 JUnit 4)
  • ​@SpringJUnitConfig​(仅支持 JUnit Jupiter)
  • ​@SpringJUnitWebConfig​(仅支持JUnit Jupiter)
  • ​@TestConstructor​(仅支持 JUnit Jupiter)
  • ​@EnabledIf​(仅支持 JUnit Jupiter)
  • ​@DisabledIf​(仅支持 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 Jupiter的测试套件中重复了前面的配置,则可以通过引入一个自定义组合注解来减少重复,该注解集中了Spring和JUnit Jupiter的通用测试配置,如下所示:

@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 5 中文解析之测试篇-集成测试(上)