Context Caching

一旦 TestContext 框架为测试加载了一个 ApplicationContext(或 WebApplicationContext),该上下文将被缓存并再次用于同一测试套件内声明相同唯一上下文配置的所有后续测试。要理解缓存如何工作的,理解什么是“唯一”和“测试套件”很重要。

可以通过用于加载 ApplicationContext 的配置参数的组合来唯一地标识它。因此,用于生成作为缓存上下文密钥的密钥的配置参数的唯一组合。TestContext 框架使用以下配置参数构建上下文缓存密钥:

  • locations (from @ContextConfiguration)

  • classes (from @ContextConfiguration)

  • contextInitializerClasses (from @ContextConfiguration)

  • contextCustomizers (来自 ContextCustomizerFactory)——这包括`@DynamicPropertySource` 方法以及 Spring Boot 测试支持中提供的各种特性,例如 @MockBean@SpyBean

  • contextLoader (from @ContextConfiguration)

  • parent (from @ContextHierarchy)

  • activeProfiles (from @ActiveProfiles)

  • propertySourceDescriptors (from @TestPropertySource)

  • propertySourceProperties (from @TestPropertySource)

  • resourceBasePath (from @WebAppConfiguration)

例如,如果 TestClassA@ContextConfigurationlocations(或 value)属性指定 {"app-config.xml", "test-config.xml"},则 TestContext 框架将加载相应的 ApplicationContext,并将其存储在仅根据这些位置的键下的静态上下文缓存中。所以,如果 TestClassB 也为其位置定义 {"app-config.xml", "test-config.xml"}(显式或隐式通过继承),但没有定义 @WebAppConfiguration、不同的 ContextLoader、不同的活动配置文件、不同的上下文初始化程序、不同的测试属性来源或不同的父上下文,那么相同的 ApplicationContext 将由这两个测试类共享。这意味着加载应用程序上下文的设置成本只发生一次(每个测试套件),后续测试执行会快很多。

Test suites and forked processes

Spring TestContext 框架将应用程序上下文存储在一个静态缓存中。这意味着上下文从字面上存储在一个 static 变量中。换句话说,如果测试在不同的进程中运行,静态缓存会在每次测试执行之间被清除,这有效地禁用了缓存机制。 要从缓存机制中受益,所有测试都必须在同一进程或测试套件中运行。这可以通过在 IDE 中将所有测试作为一个组执行来实现。同样,在使用诸如 Ant、Maven 或 Gradle 的构建框架执行测试时,确保构建框架不会在测试之间分叉非常重要。例如,如果 Maven Surefire 插件的 forkMode设置为 always`或 `pertest,则 TestContext 框架无法在测试类之间缓存应用程序上下文,并且构建过程会因此慢得多。

上下文缓存大小受默认最大尺寸32的限制。每当达到最大尺寸时,都会使用最近最少使用(LRU)驱逐策略来驱逐和关闭陈旧的上下文。可以通过设置名为`spring.test.context.cache.maxSize`的JVM系统属性,通过命令行或构建脚本配置最大尺寸。另外,还可以通过SpringProperties机制设置相同的属性。

由于在给定的测试套件中加载大量的应用程序上下文,可能会导致套件运行时间过长,因此确切地知道已经加载和缓存了多少个上下文通常是有益的。要查看底层上下文缓存的统计信息,您可以将 org.springframework.test.context.cache 日志类别的日志级别设置为 DEBUG

在不太可能发生的情况中,如果一个测试破坏了应用程序上下文并且需要重新加载(例如,通过修改 bean 定义或应用程序对象的 state),您可以使用 `@DirtiesContext`注释您的测试类或测试方法(请参阅 Spring Testing Annotations中对 `@DirtiesContext`的讨论)。这会指示 Spring 从缓存中删除上下文并在运行需要相同应用程序上下文的下一个测试之前重建应用程序上下文。请注意, `@DirtiesContext`注释的支持由 `DirtiesContextBeforeModesTestExecutionListener`和 `DirtiesContextTestExecutionListener`提供,它们默认启用。

ApplicationContext lifecycle and console logging

当您需要调试使用 Spring TestContext Framework 执行的测试时,分析控制台输出(即,输出到 SYSOUTSYSERR 流)可能很有用。一些构建工具和 IDE 能够将控制台输出与给定的测试相关联;然而,一些控制台输出无法轻易与给定测试相关联。 关于由 Spring Framework 本身或在 ApplicationContext 中注册的组件触发的控制台日志记录,了解由 Spring TestContext Framework 在测试套件中加载的 ApplicationContext 的生命周期非常重要。 测试的 ApplicationContext`通常在准备测试类实例时加载——例如,在向测试实例的 `@Autowired`字段中注入依赖关系时。这意味着在 `ApplicationContext`初始化期间触发的任何控制台日志通常无法与单独的测试方法相关联。但是,如果根据 @DirtiesContext`语义在测试方法执行之前立即关闭上下文,则会在执行测试方法之前加载上下文的新的实例。在后一种情况下,IDE 或构建工具可能会将控制台日志与单独的测试方法相关联。 可以通过以下场景之一来关闭测试的 ApplicationContext

  • 根据 @DirtiesContext 语义关闭上下文。

  • 根据 LRU 驱逐策略,上下文被关闭,因为它已经从缓存中自动驱逐。

  • 当测试套件的 JVM 终止时,通过一个 JVM 关闭挂钩关闭上下文。

如果根据 @DirtiesContext 语义在特定测试方法后关闭上下文,IDE 或构建工具可能会将控制台日志与单独的测试方法关联。如果根据 @DirtiesContext 语义在测试类后关闭上下文,在 ApplicationContext 关闭期间触发的任何控制台日志都无法与单独的测试方法关联。同样,通过 JVM 关闭挂钩在关闭阶段触发的任何控制台日志都无法与单独的测试方法关联。 当通过 JVM 关闭挂钩关闭 Spring ApplicationContext 时,在关闭阶段执行的回调将在名为 SpringContextShutdownHook 的线程上执行。因此,如果希望在通过 JVM 关闭挂钩关闭 ApplicationContext 时禁用触发的控制台日志,则可以在日志框架中注册一个自定义过滤器,它允许忽略该线程启动的任何日志记录。