Spring framework testing文档读书笔记

2023-11-16

该文章是我读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包下包含了EnvironmentPropertySource的实现,MockEnvironmentMockPropertySource可用于那些运行在容器外,但是又依赖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包下包含了ServerHttpRequestServerHttpResponse的mock实现,可用于WebFlux应用。org.springframework.mock.web.server包下包含了ServerWebExchange的mock实现,这个实现依赖于前面两个request和response的mock对象。

MockServerHttpRequestMockServerHttpResponse都实现了想相同的抽象类。

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工具类,可以参考AopUtilsAopProxyUtils的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框架提供了对ApplicationContextWebApplicationContext加载的一致性,以及缓存这些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,作为WebApplicationContextServletContext

@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到EnvironmentPropertySources集合中。

相对于操作系统的环境变量、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```,从而恢复被修改的状态。

该注解可以用在类上,也可以用在方法上,并且通过配置methodModeclassMode属性,指定是在“之前”还是“之后”将ApplicationContext标记为dirtied。

当用在类上时,可以为classMode属性指定BEFORE_CLASSAFTER_CLASSBEFORE_EACH_TEST_METHODAFTER_EACH_TEST_METHOD四个值,其中AFTER_CLASS是默认值。当用在方法上时,可以为methodMode属性指定BEFORE_METHOD和AFTER_METHOD两个值,其中AFTER_METHOD是默认值。

@DirtiesContext@ContextHierarchy一起使用时,可以为@DirtiesContexthierarchyMode属性指定值来控制如何清理缓存,具体请见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在某一个特定的测试环境下才可用。该注解可以指定namevalue键值对,当指定的键值对与配置好的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。如果不使用该注解,则默认情况下使用的ProfileValueSourceSystemProfileValueSource

@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类,以及TestContextTestExecutionListenerSmartContextLoader接口组成。每一个test类会创建一个TestContextManager,而TestContextManager管理着一个TestContextTestContext保存了当前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...
}

如果locationsvalue属性都省略,那么框架会使用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来配置ApplicationContextinitializers属性的值是ApplicationContextInitializer的实现类的数组。

@RunWith(SpringRunner.class)
@ContextConfiguration(
    classes = TestConfig.class,
initializers = TestAppCtxInitializer.class) 
public class MyTest {
    // class body...
}
Context Configuration Inheritance(继承上下文配置)

可以为@ContextConfiguration注解的inheritLocationsinheritInitializers属性指定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注解的inheritLocationsinheritProperties属性设置boolean值,可以指定是否继承父类的property资源文件或者内联的property,默认值是true。

Loading a WebApplicationContext(加载WebApplicationContext)

@WebAppConfiguration注解可以用来加载WebApplicationContext,这个在前面的章节讲过一些基础用法,这里讲解一写它的额外功能。

ServletTestExecutionListener为web测试提供了统一的支持,它在每一个测试方法开始之前,通过RequestContextHolder设置了默认的thread local的状态,并且基于@WebAppConfiguration配置的资源创建了MockHttpServletRequestMockHttpServletResponseServletWebRequest,而且保证了MockHttpServletResponseServletWebRequest会被注入到test实例中,在test执行完成后再将其thread local的状态清除。

下面的例子展示了哪些mock可以被自动装配到test实例中,其中WebApplicationContextMockServletContext在整个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.maxSizeJVM系统变量的值来改变缓存大小。也可以通过编程式的使用SpringPropertiesAPI去设置同样的属性的值。

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里还必须配置一个PlatformTransactionManagerbean,并且需要在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里定义一个PlatformTransactionManagerbean,如果存在多个PlatformTransactionManagerbean,那么可以使用@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脚本的。

类似的,AbstractTransactionalJUnit4SpringContextTestsAbstractTransactionalTestNGSpringContextTests在内部也是使用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方法执行完成之后再执行。下面的例子中,ISOLATEDAFTER_TEST_METHOD来自Sql.TransactionModeSql.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注解的属性dataSourcetransactionManager设置值来指定数据源和事务管理器。

@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等功能。

AbstractTransactionalJUnit4SpringContextTestsAbstractJUnit4SpringContextTests的基础上添加了一些访问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

待。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Spring framework testing文档读书笔记 的相关文章

随机推荐

  • segment anything原来可以这么玩

    Segment Anything能给我们做什么 前言 内容 具体实现 成果 前言 最近 大模型的热度确实是非常非常的高 从chatgpt到segment anything 这些东西整的我这刚入门的小白确实有点懵逼 最近实在是不知道干啥 想想
  • TypeScript装饰器原理分析

    文章目录 1 前言 2 装饰器原理 2 1 类装饰器 2 2 属性饰器 2 3 方法装饰器 访问器set get也属于方法 2 4 参数装饰器 3 装饰器执行顺序 1 前言 TypeScript装饰器装饰器是一种特殊类型的声明 它能够被附加
  • python numpy中mgrid使用方法

    import numpy as np 基本介绍 np mgrid start end Sj 上述表达中start表示开始数 end表示结束数 Sj表示总共个数 实例 生成的数组是包含end和start这两个数的 np mgrid start
  • 如何在ParaView中使用编程对不同的切面进行积分计算并保存输出?

    如何在ParaView中使用编程对不同的切面进行积分计算并保存输出 ParaView是一个强大的可视化和数据处理工具 它可以通过编程方式自动化各种任务 在此教程中 我们将讨论如何使用ParaView的Python编程接口来对不同的切面进行积
  • 谈谈我对redis事务的理解

    redis事务的所有命令都是序列化 有序地执行 在事务的执行过程中 不会被其他客户端发送的命令所打断 事务的主要作用就是串联所有命令防止其他命令插队 redis事务有几个常用的命令 首先是multi命令 它标记着事务的开始 意思是将命令入命
  • 恒源云GPU租用保姆级教程,助力深度学习训练!

    文章来源 恒源云社区 专注人工智能 深度学习GPU免费加速平台 官方体验网址 https gpushare com 恒源云史上最全的平台使用教程诞生了 用实力证明咱们能唱能跳产品好用 助力大家AI训练 跑赢开学季 必看篇 初次使用恒源云的用
  • golang flag 包的使用指北

    说起 golang 的 flag 个包 我们第一反应的是什么呢 至少我曾经第一次看到 flag 包的时候 第一反应是想起写 C 语言的时候咱们用于定义一个表示的 我们一般会命名为 flag 变量 实际上 golang 的 flag 包是用于
  • 无法定位软件包问题

    在etc apt 的sources list 添加镜像源 deb http archive ubuntu com ubuntu trusty main universe restricted multiverse 然后 sudo apt g
  • 数据分析报告概述

    一 结构规范及写作 报告常用结构 1 架构清晰 主次分明 数据分析报告要有一个清晰的架构 层次分明能降低阅读成本 有助于信息的传达 虽然不同类型的分析报告有其适用的呈现方式 但总的来说作为议论文的一种 大部分的分析报告还是适用总 分 总 的
  • 拜占庭将军问题 原文翻译

    拜占庭将军问 作者 LESLIE LAMPORT ROBERT SHOSTAK 和 MARSHALL PEASE 斯坦福国际研究院 译者 校对 闵敏 裴奇 Elisa EthFans org 可靠的计算机系统必须具备处理故障组件的能力 以防
  • nginx反向代理ipv6网站,使其可被ipv4访问

    环境 ubuntu 18 04 1 主机本身ipv6 v4双栈 0 安装nginx sudo apt get install nginx 1 编辑nginx conf sudo vi etc nginx nginx conf 在http 段
  • 镜像下载boot.iso和dvd1.iso的区别;dnf:找不到命令;yum和dnf的区别;CentOS Stream和Linux的区别;dnf: command not found

    这里写目录标题 一 linux 的各个系列 二 End dates are coming in 2024 for CentOS Stream 8 and CentOS Linux 7 三 镜像下载boot iso和dvd1 iso的区别 四
  • [USB 3.0 报错]-高手必看!BIOS 设置中的 xHCI 模式以及 USB 2.0/3.0 的万能 Windows 驱动

    目录 关于 USB 3 0 报错符合 USB xHCI 的主机控制器 错误代码为 10 一个匪夷所思的 USB 3 0 问题 这种情况会导致哪些症状呢 破案了 这个困扰我大半年的问题其实是 intel xHCI 模式的设置问题 初识 xHC
  • 跳转控制语句

    跳转控制语句 continue 用在循环中 基于条件控制 跳过某次循环体内容的执行 继续下一次的执行 break 用在循环中 基于条件控制 终止循环体内容的执行 也就是说结束当前的整个循环 实例 public class ControlDe
  • 新手怎么做期货?一文让你找到方向

    改革开放40年以来 我国经济水平发展逐年上升 人均收入逐年增长 金融衍生品交易市场也随之逐渐繁荣 越来越多的投资者开始走进期货投资市场 其中不乏有新手不知道怎么炒期货 第一 首先要做的功课是了解自己的个性 做期货不是光靠技术 如果成功按10
  • 使用nps内网穿透的问题记录

    实现目标 将局部网 可访问互联网 设备的端口映射到公网服务器上 1 资料准备 下载nps server 和npc client 安装包 https github com ehang io nps releases 文档 https ehan
  • SpringCloud-消息驱动

    消息驱动 Spring Cloud Stream 概述 常见MQ 消息中间件 ActiveMQ RabbitMQ RocketMQ Kafka 有没有一种新的技术诞生 让我们不再关注具体MQ的细节 我们只需要用一种适配绑定的方式 自动的给我
  • 高并发模拟~多个线程同时发起请求

    高并发模拟 多个线程同时发起请求 两种方案 CyclicBarrier 栅栏 所有的线程必须同时到达栅栏位置 才能继续执行 CountDownLatch 计数器 一个线程或多个线程一直等待 直到其他线程执行的操作完成 1 CyclicBar
  • 【Mo&AI TIME 人工智能技术博客】矛与盾的对决——神经网络后门攻防

    本篇文章内容转载于 AI TIME论道 公众号 秉持着合作共享的信念 希望给热爱人工智能的你们 提供更全面 前沿的人工智能和学科发展资讯 2022年7月9日 AI TIME组织了Ph D Debate第十一期 题为 矛与盾的对决 神经网络后
  • Spring framework testing文档读书笔记

    该文章是我读Spring testing英文官方文档的读书笔记 方便以后快速的回忆文档里讲述的内容 而不用再去读一遍官方文档 文章内容精简掉了官方文档的一些比较浅显易懂的用法以及一些很细节的地方 一半是翻译 然后加入部分自己的理解 可以使读