上下文缓存

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

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

  • locations(来自@ContextConfiguration

  • classes(来自@ContextConfiguration

  • contextInitializerClasses(来自@ContextConfiguration

  • contextCustomizers(来自ContextCustomizerFactory)——这包括@DynamicPropertySource方法以及来自 Spring Boot 测试支持的各种功能,例如@MockBean@SpyBean

  • contextLoader(来自@ContextConfiguration

  • parent(来自@ContextHierarchy

  • activeProfiles(来自@ActiveProfiles

  • propertySourceDescriptors(来自@TestPropertySource

  • propertySourceProperties(来自@TestPropertySource

  • resourceBasePath(来自@WebAppConfiguration

例如,如果TestClassA@ContextConfigurationlocations(或value)属性指定了{"app-config.xml", "test-config.xml"},则 TestContext 框架会加载相应的ApplicationContext并将其存储在基于这些位置的键下的静态上下文缓存中。因此,如果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 框架本身或在ApplicationContext中注册的组件触发的控制台日志记录,重要的是要了解在测试套件中由 Spring TestContext 框架加载的ApplicationContext的生命周期。

测试的ApplicationContext通常在准备测试类的实例时加载——例如,将依赖项注入到测试实例的@Autowired字段中。这意味着在ApplicationContext初始化期间触发的任何控制台日志记录通常无法与单个测试方法关联。但是,如果根据@DirtiesContext语义在测试方法执行之前立即关闭上下文,则会在测试方法执行之前加载上下文的新实例。在后一种情况下,IDE 或构建工具可能会将控制台日志记录与单个测试方法相关联。

测试的ApplicationContext可以通过以下场景之一关闭。

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

  • 根据 LRU 驱逐策略,上下文已自动从缓存中驱逐而关闭。

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

如果在特定测试方法之后根据@DirtiesContext语义关闭上下文,则 IDE 或构建工具可能会将控制台日志记录与单个测试方法关联起来。如果在测试类之后根据@DirtiesContext语义关闭上下文,则在ApplicationContext关闭期间触发的任何控制台日志记录都无法与单个测试方法关联。同样,通过 JVM 关闭钩子在关闭阶段触发的任何控制台日志记录都无法与单个测试方法关联。

当 Spring ApplicationContext通过 JVM 关闭钩子关闭时,在关闭阶段执行的回调将在名为SpringContextShutdownHook的线程上执行。因此,如果您希望禁用在通过 JVM 关闭钩子关闭ApplicationContext时触发的控制台日志记录,您可能能够使用您的日志记录框架注册一个自定义过滤器,该过滤器允许您忽略该线程启动的任何日志记录。