上下文缓存

一旦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并将其存储在一个基于这些位置的键下的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提供。

应用程序上下文生命周期和控制台日志

当您需要调试使用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关闭挂钩在关闭阶段触发的任何控制台日志也无法与单个测试方法关联。

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