上下文缓存
一旦 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
为 @ContextConfiguration
的 locations
(或 value
)属性指定了 {"app-config.xml", "test-config.xml"}
,TestContext 框架会加载相应的 ApplicationContext
并将其存储在基于这些位置生成的键的 static
上下文缓存中。因此,如果 TestClassB
也为其 locations
(无论是显式定义还是通过继承隐式定义)定义了 {"app-config.xml", "test-config.xml"}
,但没有定义 @WebAppConfiguration
、不同的 ContextLoader
、不同的激活配置文件、不同的上下文初始化器、不同的测试属性源或不同的父上下文,那么这两个测试类将共享相同的 ApplicationContext
。这意味着加载应用程序上下文的设置成本仅发生一次(每个测试套件),后续的测试执行会快得多。
测试套件与分叉进程
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 定义或应用程序对象的状态),你可以在测试类或测试方法上使用 @DirtiesContext
注解(参见 Spring Testing Annotations 中关于 @DirtiesContext
的讨论)。这会指示 Spring 在运行下一个需要相同应用程序上下文的测试之前,从缓存中移除该上下文并重新构建应用程序上下文。请注意,@DirtiesContext
注解的支持由 DirtiesContextBeforeModesTestExecutionListener
和 DirtiesContextTestExecutionListener
提供,默认情况下它们是启用的。
ApplicationContext 生命周期与控制台日志记录
当你需要调试使用 Spring TestContext Framework 执行的测试时,分析控制台输出(即输出到 SYSOUT
和 SYSERR
流)可能会很有帮助。一些构建工具和 IDE 能够将控制台输出与特定的测试关联起来;然而,某些控制台输出无法轻松地与特定测试关联。
对于由 Spring 框架本身或 ApplicationContext
中注册的组件触发的控制台日志记录,了解 Spring TestContext Framework 在测试套件中加载的 ApplicationContext
的生命周期非常重要。
测试的 ApplicationContext
通常是在准备测试类实例时加载的——例如,为了执行依赖注入到测试实例的 @Autowired
字段中。这意味着在 ApplicationContext
初始化期间触发的任何控制台日志记录通常无法与单个测试方法关联。然而,如果根据 @DirtiesContext 语义在测试方法执行之前立即关闭上下文,则会在测试方法执行之前加载一个新的上下文实例。在后一种情况下,IDE 或构建工具可能会将控制台日志记录与单个测试方法关联起来。
测试的 ApplicationContext
可以通过以下场景之一关闭。
-
根据
@DirtiesContext
语义关闭上下文。 -
上下文根据 LRU 淘汰策略自动从缓存中移除而关闭。
-
当测试套件的 JVM 终止时,通过 JVM 关闭钩子关闭上下文。
如果在特定测试方法之后根据 @DirtiesContext
语义关闭上下文,IDE 或构建工具可能会将控制台日志记录与单个测试方法关联起来。如果在测试类之后根据 @DirtiesContext
语义关闭上下文,则在 ApplicationContext
关闭期间触发的任何控制台日志记录都无法与单个测试方法关联。同样,通过 JVM 关闭钩子在关闭阶段触发的任何控制台日志记录也无法与单个测试方法关联。
当 Spring ApplicationContext
通过 JVM 关闭钩子关闭时,关闭阶段执行的回调会在名为 SpringContextShutdownHook
的线程上执行。因此,如果你希望禁用通过 JVM 关闭钩子关闭 ApplicationContext
时触发的控制台日志记录,你可以向日志框架注册一个自定义过滤器,以忽略由该线程启动的任何日志记录。