该文章是我读Spring testing英文官方文档的读书笔记,方便以后快速的回忆文档里讲述的内容,而不用再去读一遍官方文档。 文章内容精简掉了官方文档的一些比较浅显易懂的用法以及一些很细节的地方,一半是翻译,然后加入部分自己的理解,可以使读者快速的了解Spring testing提供了些什么功能以及在什么情况下如何使用。
基于的Spring文档是Spring framework的testing模块,版本5.0.9。 官方网站https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/testing.html
Chapter 2. Unit Testing(单元测试)
2.1 Mock Objects (mock对象)
2.1.1 Environment(环境)
org.springframework.mock.env
包下包含了Environment
和PropertySource
的实现,MockEnvironment
和MockPropertySource
可用于那些运行在容器外,但是又依赖environment和properties的test。
2.1.2 JNDI
org.springframework.mock.jndi
包下包含了JNDI SPI的实现。
2.1.3 Servlet API
org.springframework.mock.web
包下包含了一系列的Servlet API mock对象,可用于测试web context、controller以及filter,这些mock对象比一些其他的动态mock对象(比如EasyMock)或者Servlet API mock对象(比如MockObjects)更适合与Spring Web MVC框架一起使用。
2.1.4 Spring Web Reactive
org.springframework.mock.http.server.reactive
包下包含了ServerHttpRequest
和ServerHttpResponse
的mock实现,可用于WebFlux应用。org.springframework.mock.web.server
包下包含了ServerWebExchange
的mock实现,这个实现依赖于前面两个request和response的mock对象。
MockServerHttpRequest
和MockServerHttpResponse
都实现了想相同的抽象类。
2.2 Unit Testing support Classes
2.2.1 General testing utilities(常用的测试工具类)
org.springframework.test.util
包下包含了一些可用于单元测试或者集成测试的常用工具类。
ReflectionTestUtils
类包含了一系列基于反射的工具方法。开发者可以使用它来改变常量的值、设置一个非public的属性值、调用一个非public的setter方法、调用一个非public的配置的或者生命周期回调函数等测试场景,这些场景可能发生在一下这些情况:
- 在使用JPA和Hibernate等ORM框架时访问实体类的非public的setter方法时。
- Spring对
@Autowired
、@Inject
、和@Resource
等注解的支持,使之可以让依赖注入到private和protected的field、setter方法以及配置方法上。
-
@PostConstruct
和@PreDestro
等生命周期函数的使用上。
AopTestUtils
包含了一系列基于AOP的工具方法。这些方法可以用来获取隐藏在一个或多个Spring代理之后的目标对象的引用。比如,如果我们配置了一个使用了EasyMock或者Mockito的动态mock的bean,且这个mock对象被Spring代理包装了,而我们又想直接访问该mock对象去配置其expectation以及执行验证。想了解更多的AOP工具类,可以参考AopUtils
和AopProxyUtils
的javadoc。
2.2.2 Spring MVC
org.springframework.test.web
包下包含了ModelAndViewAssert
,可以使用它来对Spring MVC的ModelAndView
对象进行单元测试。
Chapter 3 Integration Testing(集成测试)
3.1 Overview
集成测试可以帮助我们测试下列场景,而不需要依赖具体的应用服务其或者环境:
- 在Spring IOC容器中正确装配bean。
- 基于JDBC或者ORM框架的数据访问。
3.2 Goals of Integration Testing(集成测试的目标)
- 管理多个测试执行期间的Spring IOC容器缓存(Spring Ioc container caching)
- 提供test fixture实例的依赖注入(Dependency Injection of test fixture instances)
- 提供集成测试的事务管理(transaction management)
- 支持用于协助开发者编写继承测试的Spring基础类(Spring-specific base classes)
3.2.1 Context management and caching(上下文管理和缓存)
Spring的TestContext框架提供了对ApplicationContext
和WebApplicationContext
加载的一致性,以及缓存这些context。将context缓存起来是很有必要的,这样再执行的多个test case的时候,就不用多次加载资源。比如一个项目有50到100个Hibernate映射文件,那么在运行test case是,可能就需要10到20秒钟去加载它们,如果每执行一个test case就话10到20秒去加载资源,那么这样会大大降低开发者的效率。
默认情况下,TestContext加载一次ApplicationContext
后,可以将其缓存起来,每一个test suite中的所有test case,可以共同使用。每一个test suite表示运行在同一个JVM里的test case,比如运行在同一个Ant、Maven或者Gradle里的test case。但是,如果修改了bean definition,那么TestContext框架就会重新加载资源。
3.2.2 Dependency Injection of test fixture(test fixture的依赖注入)
在使用TestContext框架加载ApplicationContext
时,我们可以选择通过依赖注入配置测试类的实例。
3.2.3 Transaction management(事务管理)
如果使用真实的数据库来测试持久层,可能会导致真实数据的变化,即使是使用开发数据库来运行测试,那么如果修改了某些数据,也可能影响到还未执行的test case的结果。
默认情况下,我们不用写额外的代码,TestContext就会为每一个test case创建以及回滚事务。这是通过定义在测试aplication context里的PlatformTransactionManagerbean
来实现的。
如果我们想提交一个事务(虽然很少见),我们可以使用@Commit
注解来在执行完test case后提交事务。
3.2.4 Support classes for integration testing(继承测试的支持类)
Spring TestContext框架提供了一些抽象的支持类来简化编写继承测试。这些类可以让我们来访问ApplicationContext
以及JdbcTemplate
。
3.3 JDBC Testing Support(JDBC测试支持)
org.springframework.test.jdbc
包里的JdbcTestUtils
提供了一系列的JDBC相关的工具方法,可用来简化标准数据库的测试场景,比如如下方法:
-
countRowsInTable(..)
: 计算指定table的行数
-
countRowsInTableWhere(..)
: 使用指定的WHERE语句计算指定table的行数
-
deleteFromTables(..)
: 删除指定table的所有行
-
deleteFromTableWhere(..)
: 使用指定的WHERE语句删除指定table的行
-
dropTables(..)
: dorp指定的table
3.4 Annotations
Spring框架提供了许多可以用于单元测试或者集成测试的注解。
3.4.1 Spring Testing Annotations(Spring测试注解)
@BootstrapWith
@BootstrapWith
是一个类级别的注解,可以配置Spring testContext如何启动。可用于指定一个自定义的TestContextBootstrapper
。查看Bootstrapping the TestContext framework章节可获得更多信息。
@ContextConfiguration
@ContextConfiguration
是类级别的注解,可以用于决定如何加载和配置集成测试的ApplicationContext
。通常用于指定xml配置文件的路径或者注解配置文件的class。
@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...
}
还可以指定ContextLoader
:
@ContextConfiguration(locations= "/test-context.xml",loader= CustomContextLoader.class)
public class CustomLoaderXmlApplicationContextTests {
// class body...
}
@WebAppConfiguration
@WebAppConfiguration
是类级别的注解,用于WebApplicationContext
的集成测试。该注解仅有的作用就是确保WebApplicationContext
会被加载,而且在默认情况下使用"file:src/main/webapp"
指定web应用的根路径,该路径会被用于创建一个MockServletContext
,作为WebApplicationContext
的ServletContext
。
@ContextConfiguration
@WebAppConfiguration
public class WebAppTests {
// class body...
}
可以为@WebAppConfiguration
注解的value属性设置值,以此来指定自定义的资源路径,value的值可以是"classpath:"
和"file:"
前缀,如果不指定则默认是使用文件系统的资源。
@ContextConfiguration
@WebAppConfiguration("classpath:test-web-resources")
public class WebAppTests {
// class body...
}
注意@WebAppConfiguration
必须要和@ContextConfiguration
一起使用。
@ContextHierarchy
@ContextHierarchy
是类级别的注解,可以为ApplicationContext
的集成测试定义一个层级关系,具体请见Context hierarchies章节。
@ActiveProfiles
@ActiveProfiles
是类级别的注解,可以指定哪个bean definition profile是active状态的。
@ContextConfiguration
@ActiveProfiles("dev")
public class DeveloperTests {
// class body...
}
@ContextConfiguration
@ActiveProfiles({"dev", "integration"})
public class DeveloperIntegrationTests {
// class body...
}
更多使用方法请见Context configuration with environment profiles章节。
@TestPropertySource
@TestPropertySource
是类级别的注解,可用于指定property文件的路径或者指定内联的property。它会添加测试环境的property到Environment
的PropertySources
集合中。
相对于操作系统的环境变量、Java系统的property或者通过@PropertySource
注解指定的应用程序property,测试环境的property有更高的优先级。而内联的property拥有更高的优先级。
下面是通过文件路径指定property文件:
@ContextConfiguration
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
// class body...
}
下面是声明内联的property:
@ContextConfiguration
@TestPropertySource(properties = { "timezone = GMT", "port: 4242" })
public class MyIntegrationTests {
// class body...
}
@DirtiesContext
@DirtiesContext
可用于在特定的阶段将``````ApplicationContext标记为dirtied(脏的?)并且将其缓存清除。比如某一些test case会改变单例类的状态,那么将其标记为dirtied,可以在其执行后重新加载
ApplicationContext```,从而恢复被修改的状态。
该注解可以用在类上,也可以用在方法上,并且通过配置methodMode
和classMode
属性,指定是在“之前”还是“之后”将ApplicationContext
标记为dirtied。
当用在类上时,可以为classMode属性指定BEFORE_CLASS
、AFTER_CLASS
、BEFORE_EACH_TEST_METHOD
、AFTER_EACH_TEST_METHOD
四个值,其中AFTER_CLASS
是默认值。当用在方法上时,可以为methodMode
属性指定BEFORE_METHOD和AFTER_METHOD
两个值,其中AFTER_METHOD
是默认值。
当@DirtiesContext
和@ContextHierarchy
一起使用时,可以为@DirtiesContext
的hierarchyMode
属性指定值来控制如何清理缓存,具体请见DirtiesContext.HierarchyMode
的javadocs。
@TestExecutionListeners
@TestExecutionListeners
是类级别的注解,可以用来配置TestExecutionListener
接口的实现类,这些实现类会被TestContextManager
注册。
@ContextConfiguration
@TestExecutionListeners({CustomTestExecutionListener.class,AnotherTestExecutionListener.class})
public class CustomTestExecutionListenerTests {
// class body...
}
@Commit
@Commit
可以用在类上,也可以用在方法上,用来指定一个被事务管理的test case在执行后需要提交。其功能相当于@Rollback(false)
。
@Commit
@Test
public void testProcessWithoutRollback() {
// ...
}
@Rollback
@Rollback
可以用在类上,也可以用在方法上,用于指定一个test case的事务是否回滚,如果是true
则表示回滚,false
表示不回滚(等同于@Commit
),默认为true
。而且即使不指定@Rollback
注解,默认情况下Spring TestContext框架也会回滚每一个test case的事务。
@Rollback(false)
@Test
public void testProcessWithoutRollback() {
// ...
}
@BeforeTransaction和@AfterTransaction
@BeforeTransaction
和@AfterTransaction
注解可以用在返回类型为void
的方法上,表示在开启一个事务之前或者之后去执行这个方法。
@BeforeTransaction
void beforeTransaction() {
// logic to be executed before a transaction is started
}
@AfterTransaction
void afterTransaction() {
// logic to be executed after a transaction has ended
}
@Sql
@Sql
注解可以在类上,也可以用在方法上,用于指定一个SQL脚本,并在test case之前执行这个脚本。
@Test
@Sql({"/test-schema.sql", "/test-user-data.sql"})
public void userTest {
// execute code that relies on the test schema and test data
}
更多细节可以参考“Executing SQL scripts declaratively with @Sql”章节。
@SqlConfig
@SqlConfig
需要和@Sql
一起使用,用于指定如何解析以及执行SQL脚本。
@Test
@Sql(
scripts = "/test-user-data.sql",
config = @SqlConfig(commentPrefix = "`", separator = "@@")
)
public void userTest {
// execute code that relies on the test data
}
@SqlGroup
@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 Standard Annotation Support(标准注解的支持)
下面列出的这些注解,都是Spring的标准注解,在Spring TestContext框架中,可以像在普通情况下使用它们一样使用:
@Autowired
@Qualifier
-
@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
但是,像@PostConstruct
和@PreDestroy
这样的生命周期方法,在测试类中使用会受到一定的限制。如果一个测试类中的一个方法被标记了@PostConstruct
,那么它会在所有的before方法之前被执行。而如果一个方法被标记了@PreDestroy
,那么它根本不会被执行。
3.4.3 Spring JUnit 4 Testing Annotations(Spring JUnit 4测试注解)
以下这些注解只能和“SpringRunner”、“Spring’s JUnit 4 rules”或者“Spring’s JUnit 4 support classes”一起使用。
@IfProfileValue
@IfProfileValue
注解可以指明被标记的test case在某一个特定的测试环境下才可用。该注解可以指定name
与value
键值对,当指定的键值对与配置好的ProfileValueSource
返回的相应键值对能够匹配上,该test case才可用,否则该test case会被忽略。
@IfProfileValue
注解可以使用在类或者方法上,使用在类上时比使用在方法上有更高的优先级。如果同时使用在类上和方法上,那么需要这两处的键值对都能匹配,该test case才是可用的。如果某个test case不使用@IfProfileValue
则表示其是可用的。该注解与JUnit 4的@Ignore
注解有相似的作用,@Ignore
表示某个test case总是不可用的。
@IfProfileValue(name="java.vendor", value="Oracle Corporation")
@Test
public void testProcessWhichRunsOnlyOnOracleJvm() {
// some logic that should run only on Java VMs from Oracle Corporation
}
也可以指定多个value:
@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
是类级别的注解,可以指定具体类型的ProfileValueSource
,用于获得profile。如果不使用该注解,则默认情况下使用的ProfileValueSource
是SystemProfileValueSource
。
@ProfileValueSourceConfiguration(CustomProfileValueSource.class)
public class CustomProfileValueSourceTests {
// class body...
}
@Timed
@Timed
是方法级别的注解,表示test case需要在指定时间内(毫秒)执行完成,否则实行失败。指定的时间包括该测试用例本身,以及任何重复测试(参考@Repeat
)、任何set up或者tear down步骤。
@Timed(millis=1000)
public void testProcessWithOneSecondTimeout() {
// some logic that should not take longer than 1 second to execute
}
Spring的@Timed
注解和JUnit4的@Test(timeout=...)
有一点区别。Spring的@Timed
注解,在超时后还会等待该test case执行完成在返回失败,但是JUnit4的@Test(timeout=...)
会在超时后直接返回失败,原因是JUnit4是在不同的线程中执行test case。
@Repeat
@Repeat
是方法级别的注解,表示该test case会被重复执行多次。重复执行的内容除了该test case本身,还包括任何的set up和tear down步骤。
@Repeat(10)
@Test
public void testProcessRepeatedly() {
// ...
}
3.4.4 Spring JUnit Jupiter Testing Annotations
以下的注解都只能运行在“SpringExtension”或者JUnit Jupiter(JUnit5)中,就不过多讲解。
@SpringJUnitConfig
@SpringJUnitWebConfig
@EnabledIf
@DisabledIf
3.4.5 Meta-Annotation Support for Testing(测试的元注解支持)
以下这些注解,都可以作为元注解标记到用户自定义的注解上,达到简化代码的目的。
@BootstrapWith
@ContextConfiguration
@ContextHierarchy
@ActiveProfiles
@TestPropertySource
@DirtiesContext
@WebAppConfiguration
@TestExecutionListeners
@Transactional
@BeforeTransaction
@AfterTransaction
@Commit
@Rollback
@Sql
@SqlConfig
@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)
-
@EnabledIf
(only supported on JUnit Jupiter)
-
@DisabledIf
(only supported on JUnit Jupiter)
例如,下面的配置,可能会重复的使用在多个test suite上:
@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 { }
我们可以通过引入一个用户自定义的注解:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@ContextConfiguration({"/app-config.xml", "/test-data-access-config.xml"})
@ActiveProfiles("dev")
@Transactional
public @interface TransactionalDevTestConfig { }
然后将其配置到test suite上:
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class OrderRepositoryTests { }
@RunWith(SpringRunner.class)
@TransactionalDevTestConfig
public class UserRepositoryTests { }
3.5 Spring TestContext Framework(Spring TestContext框架)
Spring TestContext框架(位于org.springframework.test.context
包)提供了对泛型、注解驱动的支持。
3.5.1 Key abstractions(关键抽象)
Spring TestContext框架的核心由TestContextManager
类,以及TestContext
、TestExecutionListener
和SmartContextLoader
接口组成。每一个test类会创建一个TestContextManager
,而TestContextManager
管理着一个TestContext
,TestContext
保存了当前test case的上下文。TestContextManager
还会更新TestContext
的状态,并且委托给TestExecutionListener
的实现,该实现提供了依赖注入、事务管理等功能。SmartContextLoader
负责为test类加载ApplicationContext
。
TestContext
TestContext
封装了一个被执行的test cast的上下文,为其负责的测试实例提供了对上下文的管理以及缓存支持,而且它对实际使用的测试框架没有感知。TestContext
会委托SmartContextLoader
去加载它需要的ApplicationContext
。
TestContextManager
TestContextManager
是Spring TestContext框架的主要入口点,负责管理TestContext
,以及在具体的执行点通知事件给已经注册的TestExecutionListener
。
TestExecutionListener
TestExecutionListener
定义了监听TestContextManager
事件的API。
Context Loaders
ContextLoader
是在Spring2.5引入的加载ApplicationContext
的策略接口,在Spring3.1有引入了功能更强大的SmartContextLoader
接口。SmartContextLoader
接口提供了对注解类、active profile、test property source、context hierarchy以及WebApplicationContext
的支持。Spring提供了很多SmartContextLoader
接口的实现类,这里就不一一介绍。
3.5.2 Bootstrapping the TestContext framework(启动TestContext框架)
虽然默认的配置已经使用与大多数情况,但是有时开发者会遇到修改ContextLoader
,实现自定义的Context
等情况,这时TestContext框架的启动就需要做一点调整。Spring为TestContext框架的启动提供了TestContextBootstrapper
接口作为SPI。一个TestContextBootstrapper
会被TestContextManager
用于加载TestExecutionListener
的实现类以及创建TestContext
。我们可以使用@BootstrapWith
注解指定用户自定义的启动策略类,如果不指定,那么框架会根据是否使用了@WebAppConfiguration
决定使用DefaultTestContextBootstrapper
还是WebTestContextBootstrapper
。
3.5.3 TestExecutionListener configuration(TestExecutionListener配置)
Spring默认提供并注册了以下TestExecutionListener
接口的实现:
-
ServletTestExecutionListener
: 为WebApplicationContext
配置了Servlet API的mock
-
DirtiesContextBeforeModesTestExecutionListener
: 处理@DirtiesContext
注解的before模式
-
DependencyInjectionTestExecutionListener
: 为test实例提供依赖注入
-
DirtiesContextTestExecutionListener
: 处理@DirtiesContext
注解的after模式
-
TransactionalTestExecutionListener
: 为事务执行提供默认的回滚策略
-
SqlScriptsTestExecutionListener
: 执行由@Sql
注解提供的SQL脚本
Registering custom TestExecutionListeners(注册自定义TestExecutionListener)
通过@TestExecutionListeners
注解可以注册自定义的TestExecutionListener
。
Automatic discovery of default TestExecutionListeners(自动发现默认的TestExecutionListener)
Spring4.1之后,还提供了SpringFactoriesLoader
机制来自动发现TestExecutionListener
。Spring在spring-test module的META-INF/spring.factories
文件中就列出了所有默认的TestExecutionListener
。用户也可以在自己工程的ETA- INF/spring.factories
文件中提供需要自动发现的TestExecutionListener
。这其实就是SPI的使用方式。
Ordering TestExecutionListeners(TestExecutionListener的顺序)
通过前面提到的SpringFactoriesLoader
机制实现的自动发现TestExecutionListener
的能力,Spring还提供了Ordered
接口和@Order
注解来指定其顺序。用户自定义的TestExecutionListener
可以通过为上述接口或者注解指定值来指定TestExecutionListener
的顺序。
Merging TestExecutionListeners(合并TestExecutionListener)
如果用户通过@TestExecutionListeners
注解注册了自定义的TestExecutionListener
,那么前面提到的默认的TestExecutionListener
就不会再被注册,这会导致开发者需要手动指定上述默认的listener:
@ContextConfiguration
@TestExecutionListeners({
MyCustomTestExecutionListener.class,
ServletTestExecutionListener.class,
DirtiesContextBeforeModesTestExecutionListener.class,
DependencyInjectionTestExecutionListener.class,
DirtiesContextTestExecutionListener.class,
TransactionalTestExecutionListener.class,
SqlScriptsTestExecutionListener.class
})
public class MyTest {
// class body...
}
可以为@TestExecutionListeners
注解的mergeMode
属性指定MergeMode.MERGE_WITH_DEFAULTS
来告诉框架将当前listener与默认的listener合并。
@ContextConfiguration
@TestExecutionListeners(
listeners = MyCustomTestExecutionListener.class,
mergeMode = MERGE_WITH_DEFAULTS
)
public class MyTest {
// class body...
}
3.5.4 Context management(上线文管理)
Context configuration with XML resources(使用XML资源文件配置上下文)
为@ContextConfiguration
注解的locations
属性设置值,可以指定加载ApplicationContext
的xml文件的路径。路径格式类似context.xml
的,表示类路径下当前文件的相对路径的资源;/org/example/config.xml
这种格式表示类路径下绝对路径的资源。
例子:
@RunWith(SpringRunner.class)
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"})
public class MyTest {
// class body...
}
也可以把locations
属性值省略:
@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"})
public class MyTest {
// class body...
}
如果locations
和value
属性都省略,那么框架会使用GenericXmlContextLoader
或者GenericXmlWebContextLoader
去自动定位资源文件,定位规则为:如果我们的类叫做com.example.MyTest
,那么GenericXmlContextLoader
会加载classpath:com/example/MyTest-context.xml
路径下的资源文件。
Context configuration with annotated classes(使用注解类配置上下文)
可以为@ContextConfiguration
注解提供被注解的配置类来指定ApplicationContext
资源:
@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class})
public class MyTest {
// class body...
}
Context Configuration with Context Initializers(使用Context Initializer配置上下文)
通过给@ContextConfiguration
注解的initializers
属性赋值,可以为我们的test case指定context initializer来配置ApplicationContext
,initializers
属性的值是ApplicationContextInitializer
的实现类的数组。
@RunWith(SpringRunner.class)
@ContextConfiguration(
classes = TestConfig.class,
initializers = TestAppCtxInitializer.class)
public class MyTest {
// class body...
}
Context Configuration Inheritance(继承上下文配置)
可以为@ContextConfiguration
注解的inheritLocations
和inheritInitializers
属性指定boolean值来表示当前test类是否继承父类的配置,默认值是true
。可继承的配置包括resouce路径、基于注解的配置类、context initializer。
Context Configuration with Environment Profiles(使用环境profile配置context)
使用@ActiveProfiles
注解,可以为test类指定一系列可用的profile,下面的代码就指定了使用dev
的profile。
@RunWith(SpringRunner.class)
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {
@Autowired
private TransferService transferService;
@Test
public void testTransferService() {
// test the transferService
}
}
还可以给@ActiveProfiles
注解的inheritProfiles
属性指定boolean值,表示是否继承父类可用profile。下面的代码表示不继承:
@ActiveProfiles(profiles = "production", inheritProfiles = false)
public class ProductionTransferServiceTest extends AbstractIntegrationTest {
// test body
}
有时候,我们会需要通过编程来指定可用的profile而不是通过声明,这时候,我们可以提供一个ActiveProfilesResolver
接口的实现类并且将其赋值到@ActiveProfiles
注解的resolver
属性上来实现编程式指定可用profile,下面是一个例子:
@ActiveProfiles(
resolver = OperatingSystemActiveProfilesResolver.class,
inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
// test body
}
public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {
@Override
String[] resolve(Class<?> testClass) {
String profile = ...;
// determine the value of profile based on the operating system
return new String[] {profile};
}
}
Context configuration with test property sources(使用test property source配置上下文)
使用@TestPropertySource
注解可以为test类配置测试环境下使用的property资源文件。
给该注解的locations
或者value
属性赋值,可以指定资源文件的路径,每一个资源文件都会被解析为一个Spring的Resource
。带斜杠的路径表示类路径下的绝对路径,例如:/org/example/test.xml
;不带斜杠的表示在类路径下,相对于当前文件的相对路径,例如:"test.properties"
,还可以为路径指定前缀(classpath:
, file:
, http:
等)。路径中不支持通配符,比如*/.properties
,必须明确指定资源的完整路径。
@ContextConfiguration
@TestPropertySource("/test.properties")
public class MyIntegrationTests {
// class body...
}
@TestPropertySource
还可以指定内联的property键值对。内联的property相对于通过资源文件添加的property拥有更高的优先级,可以使用下列三种形式表示:
- “key=value”
- “key:value”
- “key value”
@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242",})
public class MyIntegrationTests {
// class body...
}
通过给@TestPropertySource
注解的inheritLocations
和inheritProperties
属性设置boolean值,可以指定是否继承父类的property资源文件或者内联的property,默认值是true。
Loading a WebApplicationContext(加载WebApplicationContext)
@WebAppConfiguration
注解可以用来加载WebApplicationContext,这个在前面的章节讲过一些基础用法,这里讲解一写它的额外功能。
ServletTestExecutionListener
为web测试提供了统一的支持,它在每一个测试方法开始之前,通过RequestContextHolder
设置了默认的thread local的状态,并且基于@WebAppConfiguration
配置的资源创建了MockHttpServletRequest
、MockHttpServletResponse
和ServletWebRequest
,而且保证了MockHttpServletResponse
和ServletWebRequest
会被注入到test实例中,在test执行完成后再将其thread local的状态清除。
下面的例子展示了哪些mock可以被自动装配到test实例中,其中WebApplicationContext
和MockServletContext
在整个test suite中都会被缓存起来,而别的则不会:
@WebAppConfiguration
@ContextConfiguration
public class WacTests {
@Autowired
WebApplicationContext wac; // cached
@Autowired
MockServletContext servletContext; // cached
@Autowired
MockHttpSession session;
@Autowired
MockHttpServletRequest request;
@Autowired
MockHttpServletResponse response;
@Autowired
ServletWebRequest webRequest;
//...
}
Context caching(上下文缓存)
TestContext framework会将加载过的ApplicationContext
或者WebApplicationContext
缓存起来,供同一个test suite中的其他test case使用。Spring会使用下列配置的参数作为缓存context的key:
-
locations
(from @ContextConfiguration
)
-
classes
(from @ContextConfiguration
)
-
contextInitializerClasses
(from @ContextConfiguration
)
-
contextCustomizers
(from ContextCustomizerFactory
)
-
contextLoader
(from @ContextConfiguration
)
-
parent
(from @ContextHierarchy
)
-
activeProfiles
(from @ActiveProfiles
)
-
propertySourceLocations
(from @TestPropertySource
)
-
propertySourceProperties
(from @TestPropertySource
)
-
resourceBasePath
(from @WebAppConfiguration
)
比如,如果TestClassA指定了@ContextConfiguration(locations={"app-config.xml", "test-config.xml"}),那么框架会唯一的使用一个key将他们缓存在一个
static上下文缓存里。这时,如果TestClassB也使用了同样的注解以及值,并且没有定义
@WebAppConfiguration,没有使用不同的
ContextLoader、可用profile、context initializer、测试property source以及parent context,那么框架会在这两个类之间共享
ApplicationContext```。
默认情况下,框架最多只会缓存32个context,可以设置spring.test.context.cache.maxSize
JVM系统变量的值来改变缓存大小。也可以通过编程式的使用SpringProperties
API去设置同样的属性的值。
Context hierarchies(上下文继承)
这章和之前讲解@ContextHierarchy
注解的章节内容差不多,额外讲了一些细节,这里不再单独讲解。
3.5.5 Dependency injection of test fixtures(test fixture的依赖注入)
该章节介绍了在test类中可以使用@Autowired
等注解达到依赖注入的功能,额外讲了一些细节,这里不再单独讲解。
3.5.6 Testing request and session scoped beans(测试request和session作用域的bean)
该章节设计到web相关的内容,先暂时不讲解,有需求的时候再研究。
3.5.7 Transaction management(事务管理)
TestContext框架默认会注册TransactionalTestExecutionListener
,以此来管理事务。但是除此之外,在我们的测试类里加载的ApplicationContext
里还必须配置一个PlatformTransactionManager
bean,并且需要在test方法或者test类上使用@Transactional
注解。
Enabling and disabling transactions(启用/禁用事务)
将@Transactional
使用到test方法上,则表示该方法被事务管理。将@Transactional
使用在test类上,则表示该类以及其子类的所有方法都可以被事务管理。如果一个方法或者其所在的类(包括父类)没有被@Transactional
注解标记,那么它不会被事务管理。注意,即使使用了@Transactional
注解,但是如果传播级别是NOT_SUPPORTED
,那么该方法也不会被事务管理。
Transaction rollback and commit behavior(事务的回滚和提交行为)
默认情况下,一个test事务会在方法执行完成后回滚,如果我们想要提交事务,那么需要使用@Commit
或者@Rollback
注解。
Programmatic transaction management(编程式事务管理)
使用TestTransaction
,可以实现test编程式事务管理。暂时用不上,这里不再讲解。
Executing code outside of a transaction(在事务外执行额外代码)
有时候我们需要在事务开始或者结束之后执行一些额外的代码,这时可以在返回值为void
的方法上使用@BeforeTransaction
或者@AfterTransaction
注解,那么TransactionalTestExecutionListener
就会在事务开始或结束后执行被标记的方法。
Configuring a transaction manager(配置事务管理器)
TransactionalTestExecutionListener
需要加载的ApplicationContext
里定义一个PlatformTransactionManager
bean,如果存在多个PlatformTransactionManager
bean,那么可以使用@Transactional("myTxMgr")
或者@Transactional(transactionManager = "myTxMgr")
指定需要使用的bean。或者可以让被@Configuration
标记的类实现TransactionManagementConfigurer
接口,详情请见TestContextTransactionUtils.retrieveTransactionManager()
。
3.5.8 Executing SQL scripts(执行SQL脚本)
Executing SQL scripts programmatically(使用编程式的方式执行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脚本的解析以及执行的情况。
ResourceDatabasePopulator
提供了一套基于对象的API,这套API使用外部的SQL脚本来编程式的填充、初始化或者清理数据库。并且还提供了配置字符编码、statement分隔符、comment分隔符以及错误处理标记,每一个配置都有默认的值。下面是例子:
@Test
public 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
在内部也是使用ResourceDatabasePopulator
执行SQL脚本的。
Executing SQL scripts declaratively with @Sql(使用@Sql声明式执行SQL脚本)
SqlScriptsTestExecutionListener
提供了对@Sql
的支持,使用@Sql
注解可以声明式执行SQL脚本。
Path resource semantics(路径资源语义用法)
任何一个path都会被解析为Spring的Resource
。带斜杆开头的路径表示从类路径开始的绝对路径,例如:"/org/example/schema.sql"
,不带斜杆开头的表示从当前文件开始的相对路径,例如:"schema.sql"
。还可以为路径加上前缀,这样Spring会使用相应的协议加载资源,比如classpath:
、file:
、http:
。
下面是一个例子:
@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {
@Test
void 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
}
}
Default script detection(默认的脚本发现策略)
如果@Sql
没有明确的指定SQL脚本,框架会尝试去寻找默认的脚本,如果找不到,则会抛出IllegalStateException
异常。
- 注解声明在类上:如果test类名是
com.example.MyTest
,那么需要脚本的路径是"classpath:com/example/MyTest.sql"
。
- 注解声明在方法上:如果该方法是
com.example.MyTest#testMethod()
,那么需要的脚本的路径是"classpath:com/example/MyTest.testMethod.sql"
。
Declaring multiple @Sql sets(声明多个@Sql)
有时候我们需要指定多个SQL脚本,并且多个SQL脚本之间有不同的配置、不同的错误处理规则、不同的执行阶段等。这时候我们需要声明多个@Sql
,如果是JAVA8,那么可以之间声明多个@Sql
注解,否则需要使用@SqlGroup
。
下面是例子:
@Test
@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
}
@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
}
Script execution phases(脚本执行阶段)
默认情况下,SQL脚本都会在test方法之前被执行。可以通过为@Sql
注解的executionPhase
属性设置AFTER_TEST_METHOD
,让SQL脚本在test方法执行完成之后再执行。下面的例子中,ISOLATED
和AFTER_TEST_METHOD
来自Sql.TransactionMode
和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
)
public void userTest {
// execute code that needs the test data to be committed
// to the database outside of the test's transaction
}
Script configuration with @SqlConfig(使用@SqlConfig配置脚本)
通过@Sql
注解的config
属性设置的配置,只对当前被注解的方法有效。
我们可以使用@SqlConfig
注解全局配置SQL脚本,@SqlConfig
需要使用在test类上,这时该test类以及其子类,都会继承其配置。每一个@SqlConfig
的属性都有一个默认值,如果子类需要覆盖父类的某个属性为默认值,那么可以将属性的值谢伟""
获得DEFAULT
。
Transaction management for @Sql(@Sql的事务管理)
@Sql
里的脚本也可以被事务管理起来。还可以为@SqlConfig
注解的属性dataSource
和transactionManager
设置值来指定数据源和事务管理器。
@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {
final JdbcTemplate jdbcTemplate;
@Autowired
TransactionalSqlScriptsTests(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.");
}
}
3.5.10 TestContext Framework Support Classes(TestContext框架支持类)
Spring JUnit 4 Runner
Spring提供了与JUnit4完全集成的自定义runner(支持JUnit 4.12以及更高版本),用户只用使用@RunWith(SpringJUnit4ClassRunner.class)
或者@RunWith(SpringRunner.class)
注解标记test类即可。
下面是一个最简化的例子,展示如何使用自定义的runner配置test类:
@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {
@Test
public void testMethod() {
// execute test logic...
}
}
注意,@TestExecutionListeners
配置了一个空的list,禁用了所有默认的listener。
Spring JUnit 4 Rules
org.springframework.test.context.junit4.rules
包提供了JUnit4的rule:
SpringClassRule
-
SpringMethodRule
SpringClassRule
是一个JUnitTestRule
,支持类级别的特性。SpringMethodRule
是一个JUnitMethodRule
,支持实例级别以及方法级别的特性。
Spring的基于rule的JUnit支持,拥有所有SpringRunner
的优点,而且可以和一些其他的runner配合使用,比如Junit4的Parameterized
以及MockitoJunitRunner
。下面是例子:
// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {
@ClassRule
public static final SpringClassRule springClassRule = new SpringClassRule();
@Rule
public final SpringMethodRule springMethodRule = new SpringMethodRule();
@Test
public void testMethod() {
// execute test logic...
}
}
JUnit 4 Support Classes
org.springframework.test.context.junit4
包为基于JUnit4的test类提供了以下支持类:
AbstractJUnit4SpringContextTests
AbstractTransactionalJUnit4SpringContextTests
AbstractJUnit4SpringContextTests
是一个抽象类,test类继承它后,可以访问protected applicationContext
实例变量来实现查找bean等功能。
AbstractTransactionalJUnit4SpringContextTests
在AbstractJUnit4SpringContextTests
的基础上添加了一些访问JDBC的功能。
SpringExtension for JUnit Jupiter
这一章节是JUnit5的内容,待。
用到了@ExtendWith(SpringExtension.class)
、@SpringJUnitConfig
。
Dependency Injection with SpringExtension
这一章节也是JUnit5的内容,待。
3.6 Spring MVC Test Framework(Spring MVC测试框架)
待。
3.7 WebTestClient
待。
3.8 PetClinic Example
待。