Spring Test官方部分文档翻译
这一章主要讲解了Spring框架对集成测试的支持以及单元测试的最优练习。Spring团队倡导测试驱动开发模式(TDD),在实际使用中发现,正确的使用IOC能让单元模块和功能集成的测试变得更加简单。
1.Sping test框架简介
测试是企业软件项目开发中必不可少的一部分。这一小节只要关注IOC容器整合单元测试的原理以及spring框架对集成测试支持所带来的好处。
2.Unit Testing 单元测试
相较于传统的JAVA EE开发模式,依赖注入这种开发模式减少了你的代码和容器之间的耦合性。构成应用的那些POJOs(普通java对象)应该是容易使用JUnit或TestNG测试框架进行测试的。你能够使用mock objects 在独立环境中测试你的代码。如果你遵循了Spring团队对架构设计的一些建议,那么你代码库中存在的清晰应用分层和组件化模块能够简化单元测试的难度。比如,你能够通过模拟DAO或数据仓库所提供的接口来测试服务层的对象,而不需要在运行单元测试时直接去访问数据库中的数据。
真实的单元测试运行时十分迅速。特别注意,正确的单元测试能够提高你的工作效率。在特定的测试场景中,spring会提供mock object(模拟对象)和测试集成类来帮助你进行测试。
2.1.Mock Objects 模拟对象
spring包含和模拟测试相关的一系列软件包,包括Environment(环境)、JNDI(内置数据源)、Servlet API、Spring Web Reactive。
2.1.1 Environment(环境)
org.springframework.mock.env包包含了Environment和PropertySource抽象类的模拟实现。MockEnvironment和MockPropertySource是两个非常有用的工具类,当你的测试环境脱离spring容器,需要运行在特定的环境中时,这两个工具类对于你的测试非常有用。
2.1.2 JNDI(内置数据源)
org.springframework.mock.jndi包包含了JNDI SPI的实现,使用这个包能够帮你建立测试套件或独立应用程序的简易JNDI环境。举个例子,JDBC DataSource实例能够和相同的JNDI 名称绑定在仪器,这样的话你在进行测试时就能够重复利用已有的代码和配置。
2.1.3 Servlet API
org.springframework.mock.web包包含了一组Servlet API相关的模拟对象,这一组对象主要同来测试web context(web上下文)、controllers(控制)和filters。这些mock objects主要是针对Spring MVC框架而开发的,而且在测试web框架时也更加方便。 注意:从spring5.0开始,该包中的API是建立在Servlet4.0开发的
Spring MVC框架的测试是建立在mock Servlet API对象上的
2.1.4 Spring Web Reactive
org.springframework.mock.http.server.reactive包包含了ServerHttpRequest和ServerHttpResponse对象的模拟实现。org.springframework.mock.web.server包包含了一个模拟的ServerWebExchange,该对象需要依赖于之前的那些模拟request和response对象。所有的MockServerHttpRequest和MockServerHttpResponse都从一样的抽象基类继承而来。例如,一个mock request一旦创建完成就是不可变的了,但是你能够使用ServerHttpRequest的mutate方法创建一个可修改实例。
2.2 Unit Testing Support Classes 单元测试支持类
Spring包含一系列单元测试辅助类。
- General Testing Utilities
- Spring MVC Testing Utilities
2.2.1 General Testing Utilities 通用测试工具类
org.springframework.test.util包包含了应用于单元或集成测试时的一些通用工具类。ReflectionTestUtils(反射测试工具类)是一个工具方法的集合,原理是反射。这些工具类主要应用在以下场景
- 需要对常数值进行修改
- 设置一个对象的非公有域
- 调用一个非公有的方法
- 调用一个非公有的配置信息
- 测试应用代码时所产生的那些生命周期回调函数
5.1. ORM 框架
5.2. Spring支持的注解,如@Autowired,@Inject,and @Resource,这些应用在private、protected数据域、set方法和配置方法的注解。
5.3. @PostConstruct and @PreDestroy 使用注解注释的生命周期回调函数。 AopTestUtils适合AOP相关的工具类集合。使用这些工具类去获取隐藏在Spring 代理对象背后的那些目标对象(就是被代理对象),例如,你已经将一个bean配置为了一个模拟对象,这个模拟对象使用Spring代理包装过的,你可能需要直接访问底层的模拟对象,以便配置异常和进行验证。
2.2.2 Spring MVC Testing Utilities Spring MVC框架测试工具类
org.springframework.test.web包含了ModelAndViewAssert,使用该类整合JUnit、TestNG等测试框架,实现对ModelAndView对象的测试。
注意:对Spring MVC Controllers进行单元测试 为了测试Spring MVC Controllers类,要将ModelAndViewAssert与MockHttpServletRequest、 MockHttpSession、以及一系列Servlet API 模拟相关的对象进行整合。
3.集成测试
包含以下小节
- 概览
- 集成测试目的
- JDBC测试支持
- 注解
- Spring 测试上下文框架 Spring TestContext Framework
- spring mvc测试框架 Spring MVC Test Framework
- PetClinic Example 宠物诊所实例
3.1 概览
3.2 集成测试目的
目的:
- 在测试时管理spring IoC容器缓存
- 提供需要被测试的那些固定实例的依赖注入
- 提供事务管理
- 提供spring基类,帮助开发者编写集成测试
3.2.1 上下文管理和缓存
Spring TestContext Framework提供ApplicationContext、WebApplicationContext实例以及其缓存的统一加载。举例:工程在启动时需要加载大量的映射文件、实例化对象,这是非常耗时间的,严重影响工作效率,而spring测试框架能够支持上下文缓存,就避免了spring容器启动所消耗的大量时间,也就是应用上下文只加载一次,加载完成之后的缓存重复利用
3.2.2 固定测试对象的依赖注入
顾名思义,通过预配置的方式,将几个需要测试的固定实例添加到应用上下文,然后在不同的测试环境下重复使用应用上下文(重复利用的实例包含事务代理、数据源实例等)。
3.2.3 事务管理
在测试持久化API时,会修改数据库的数据,从而可能会影响后续的正常测试。所以spring测试框架能够帮助你进行事务的提交和回滚。不过要想支持测试框架的事务特性,就需要在你的应用上下文中定义一个PlatformTransactionManager(平台事务管理)
3.2.4 提供支持集成测试类
spring提供了几个抽象类,用于简化测试类的编写。这些测试基类提供了几个钩子函数,能够让你访问某些关键对象、方法和变量,例如ApplicationContext、JdbcTemplate。
3.3 JDBC测试支持
jdbc测试工具类为JdbcTestUtils,在org.springframework.test.jdbc包中。JdbcTestUtils提供了一下重要的静态工具方法。
countRowsInTable(..)统计表中行数
countRowsInTableWhere(..)根据限制条件统计行数
deleteFromTables(..)删除表中所有数据
deleteFromTableWhere(..)根据给定条件删除表中所有数据
dropTables(..)删除数据表
注意:AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests提供了几个方便的方法来调用上面提到的几个方法
3.4 注解
这一章主要介绍一些在进行spring应用测试时会用到的注解
3.4.1 Spring Testing Annotations spring测试框架注解
@BootstrapWith
类级别的注解,配置spring测试框架启动信息
@ContextConfiguration
定义类级别的元数据,决定如何加载和配置ApplicationContext用于集成测试。
通过locations属性声明配置文件位置,classes属性决定要加载上下文的类。
实例
加载XML文件
@ContextConfiguration("/test-config.xml")
public class XmlApplicationContextTests {
// class body...
}
加载一个类
@ContextConfiguration(classes = TestConfig.class)
public class ConfigClassApplicationContextTests {
// class body...
}
声明一个ApplicationContextInitializer类
@ContextConfiguration(initializers = CustomContextIntializer.class)
public class ContextInitializerTests {
// class body...
}
@WebAppConfiguration
类级注解,当加载的上下文为WebApplicationContext时,才能使用该注解,
然后默认将file:src/main/webapp作为web应用的根路径(也是资源的根路径)
@ContextConfiguration
@WebAppConfiguration
public class WebAppTests {
// class body...
}
指定一个不同的资源路径基地址,支持classpath:和file:两种前缀
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources")
public class WebAppTests {
// class body...
}
@WebAppConfiguration一定要和@ContextConfiguration注解同时使用
@ContextHierarchy
类注解,定义用于集成测试的ApplicationContext的等级制度。
@ContextHierarchy({
@ContextConfiguration("/parent-config.xml"),
@ContextConfiguration("/child-config.xml")
})
public class ContextHierarchyTests {
// class body...
}
@WebAppConfiguration
@ContextHierarchy({
@ContextConfiguration(classes = AppConfig.class),
@ContextConfiguration(classes = WebConfig.class)
})
public class WebIntegrationTests {
// class body...
}
@ActiveProfiles
类及注解,决定激活哪个bean definition profiles
@ContextConfiguration
@ActiveProfiles("dev")
public class DeveloperTests {
// class body...
}
激活多个profile
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
public class DeveloperIntegrationTests {
// class body...
}
@TestPropertySource
类及注解,用来指定需要加载的propertiesfiles的路径
和需要被加载到Environment中PropertySources集中的内部属性。
通过TestPropertySource注解加载的属性具有次最高优先级,
能覆盖操作系统的环境变量和java系统属性,而内部属性的优先级更高
从classpath根路径下加载
@ContextConfiguration
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
// class body...
}
声明内部属性
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class MyIntegrationTests {
// class body...
}
@DirtiesContext 肮脏的上下文
类级、方法级
在测试执行期间已经被弄脏了的Spring ApplicationContext,然后这些应用上下文应该被关闭。
弄脏:测试执行过程中修改了不应被修改的数据、损坏了数据。
当一个应用上下文被标记为dirty时,它就会从测试缓存中移除、关闭,
然后为了后续测试能够正常进行,会使用相同的配置信息重建被声明的spring容器。
在被注解的方法前后或当前测试类的前后,ApplicationContext就会被标记为dirty。
在当前测试类之前dirty context
@DirtiesContext(classMode = BEFORE_CLASS)
public class FreshContextTests {
// some tests that require a new Spring container
}
在当前测试类之后dirty context(默认或设为AFTER_CLASS)
@DirtiesContext
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
在每一个测试方法之前dirty context
@DirtiesContext(classMode = BEFORE_EACH_TEST_METHOD)
public class FreshContextTests {
// some tests that require a new Spring container
}
在每一个测试方法之后dirty context
@DirtiesContext(classMode = AFTER_EACH_TEST_METHOD)
public class ContextDirtyingTests {
// some tests that result in the Spring container being dirtied
}
在当前测试方法之前dirty context
@DirtiesContext(methodMode = BEFORE_METHOD)
@Test
public void testProcessWhichRequiresFreshAppCtx() {
// some logic that requires a new Spring container
}
在当前测试方法之后dirty context
@DirtiesContext
@Test
public void testProcessWhichDirtiesAppCtx() {
// some logic that results in the Spring container being dirtied
}
@TestExecutionListeners
配置TestExecutionListener实现到注解类上,
与@ContextConfiguration注解仪器使用,支持集成监听器
@ContextConfiguration
@TestExecutionListeners(
{
CustomTestExecutionListener.class,
AnotherTestExecutionListener.class
}
)
public class CustomTestExecutionListenerTests {
// class body...
}
@Commit
测试方法结束后,事务提交
@Rollback(false)也是一样的作用。是类级也是方法级
@Commit
@Test
public void testProcessWithoutRollback() {
// ...
}
@Rollback
事务方法执行完成后是否应该回滚。
在测试框架中,不写注解或不写值,默认都是回滚的。
类级,也是方法级,注解在方法上时会覆盖默认或类级注解
不回滚
@Rollback(false)
@Test
public void testProcessWithoutRollback() {
// ...
}
@BeforeTransaction
用在void方法上,方法要在事务开始之前执行。
必须要有一些方法已经用@Transactional注解过了
@BeforeTransaction
void beforeTransaction() {
// logic to be executed before a transaction is started
}
@AfterTransaction
用在void方法上,方法要在事务开始之后执行。
必须要有一些方法已经用@Transactional注解过了
@AfterTransaction
void afterTransaction() {
// logic to be executed after a transaction has ended
}
@Sql
用于类或方法,指定给定sql,然后再执行测试方法
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
public void userTest {
// execute code that relies on the test schema and test data
}
@SqlConfig
定义解析和运行sql脚本的规则
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@")
)
public void userTest {
// execute code that relies on the test data
}
@SqlGroup
容器,包含多个@Sql注解
@Test
@SqlGroup({
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
@Sql("/test-user-data.sql")
)}
public void userTest {
// execute code that uses the test schema and test data
}
复制代码
3.4.2 spring测试框架的通用注解,spring中也可以使用
@Autowired
@Qualifier
@Resource (javax.annotation)
@ManagedBean (javax.annotation)
@Inject (javax.inject)
@Named (javax.inject)
@PersistenceContext (javax.persistence)
@PersistenceUnit (javax.persistence)
@Required
@Transactional
3.4.3 Spring JUnit 4 Testing Annotations
3.5 Spring 测试上下文框架 Spring TestContext Framework
spring测试框架提供了泛型、注解驱动单元和集成测试的支持。这一节分为2部分,自定义测试框架和使用现有测试框架。
3.5.4 上下文管理
每一个TestContext都给测试实例提供了上下文管理、缓存支持的功能。测试实例不能自 动访问被配置了的ApplicationContext。但是,如果一个测试类实现了ApplicationContextAware接口,那么就可以访问到一个ApplicationContext的实例。注意:AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests都实现了该接口,所以他们能自动访问ApplicationContext。 所以通过继承上面两个类都可以直接访问ApplicationContext。 也可以通过@Autowired注解注入上下文对象来访问 访问ApplicationContext
@RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {
@Autowired
private ApplicationContext applicationContext;
// class body...
}
// 访问WebApplicationContext
@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration
public class MyWebAppTest {
@Autowired
private WebApplicationContext wac;
// class body...
}
复制代码
使用了测试框架的测试类不需要继承或实现特殊类来配置应用上下文。只要在测试类上标注@ContextConfiguration注解就可以实现配置。如果你的测试类没有显示声明上下文资源文件路径或注解类,默认配置器ContextLoader会自行决定文件默认的加载路径或默认的配置类。另外,application context也能通过application context initializers来配置。 接下来的基本部分会介绍通过XML、Groovy脚本、注解类和context initializers来配置测试ApplicationContext。
使用XML文件配置 路径位置没有前缀,默认为classpath。也可以添加classpath:, file:, http:前缀
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
public class MyTest {
// class body...
}
复制代码
如果你没有指定要加载文件的路径,xml加载器会从默认路径记载文件。如果你的测试类的全类名为com.example.MyTest,那么会从classpath:com/example/MyTest-context.xml下加载XML文件
package com.example;
@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration
public class MyTest {
// class body...
}
复制代码
使用注解类配置