上下文缓存

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

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

  • locations(来自 @ContextConfiguration

  • classes(来自 @ContextConfiguration

  • contextInitializerClasses(来自 @ContextConfiguration

  • contextCustomizers(来自 ContextCustomizerFactory)——这包括 @DynamicPropertySource 方法、bean 覆盖(例如 @TestBean@MockitoBean@MockitoSpyBean 等),以及 Spring Boot 测试支持中的各种功能。

  • contextLoader(来自 @ContextConfiguration

  • parent(来自 @ContextHierarchy

  • activeProfiles(来自 @ActiveProfiles

  • propertySourceDescriptors(来自 @TestPropertySource

  • propertySourceProperties(来自 @TestPropertySource

  • resourceBasePath(来自 @WebAppConfiguration

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

测试套件和分叉进程

Spring TestContext 框架将应用程序上下文存储在静态缓存中。这意味着上下文实际上存储在 static 变量中。换句话说,如果测试在独立的进程中运行,静态缓存会在每次测试执行之间被清除,这有效地禁用了缓存机制。 为了利用缓存机制,所有测试必须在同一个进程或测试套件中运行。这可以通过在 IDE 中将所有测试作为一个组来执行来实现。同样,当使用 Ant、Maven 或 Gradle 等构建框架执行测试时,重要的是要确保构建框架在测试之间不进行分叉。例如,如果 Maven Surefire 插件的 forkMode 设置为 alwayspertest,TestContext 框架将无法在测试类之间缓存应用程序上下文,导致构建过程运行速度显著变慢。

上下文缓存的大小受默认最大大小 32 的限制。当达到最大大小时,将使用最近最少使用 (LRU) 逐出策略来逐出并关闭陈旧的上下文。您可以通过设置名为 spring.test.context.cache.maxSize 的 JVM 系统属性,从命令行或构建脚本配置最大大小。作为替代方案,您可以通过 SpringProperties 机制设置相同的属性。

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

在极少数情况下,如果测试破坏了应用程序上下文并需要重新加载(例如,通过修改 bean 定义或应用程序对象的状态),您可以使用 @DirtiesContext 注解您的测试类或测试方法(请参阅 Spring 测试注解 中关于 @DirtiesContext 的讨论)。这指示 Spring 从缓存中移除上下文并在运行下一个需要相同应用程序上下文的测试之前重建应用程序上下文。请注意,对 @DirtiesContext 注解的支持由 DirtiesContextBeforeModesTestExecutionListenerDirtiesContextTestExecutionListener 提供,它们默认启用。

ApplicationContext 生命周期和控制台日志记录

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

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

  • 上下文关闭是因为它已根据 LRU 逐出策略自动从缓存中逐出。

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

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