Context Caching

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

Once the TestContext framework loads an ApplicationContext (or WebApplicationContext) for a test, that context is cached and reused for all subsequent tests that declare the same unique context configuration within the same test suite. To understand how caching works, it is important to understand what is meant by “unique” and “test suite.”

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

An ApplicationContext can be uniquely identified by the combination of configuration parameters that is used to load it. Consequently, the unique combination of configuration parameters is used to generate a key under which the context is cached. The TestContext framework uses the following configuration parameters to build the context cache key:

  • locations (from @ContextConfiguration)

  • classes (from @ContextConfiguration)

  • contextInitializerClasses (from @ContextConfiguration)

  • contextCustomizers (from ContextCustomizerFactory) – this includes @DynamicPropertySource methods as well as various features from Spring Boot’s testing support such as @MockBean and @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 将由这两个测试类共享。这意味着加载应用程序上下文的设置成本只发生一次(每个测试套件),后续测试执行会快很多。

For example, if TestClassA specifies {"app-config.xml", "test-config.xml"} for the locations (or value) attribute of @ContextConfiguration, the TestContext framework loads the corresponding ApplicationContext and stores it in a static context cache under a key that is based solely on those locations. So, if TestClassB also defines {"app-config.xml", "test-config.xml"} for its locations (either explicitly or implicitly through inheritance) but does not define @WebAppConfiguration, a different ContextLoader, different active profiles, different context initializers, different test property sources, or a different parent context, then the same ApplicationContext is shared by both test classes. This means that the setup cost for loading an application context is incurred only once (per test suite), and subsequent test execution is much faster.

Test suites and forked processes

Spring TestContext 框架将应用程序上下文存储在一个静态缓存中。这意味着上下文从字面上存储在一个 static 变量中。换句话说,如果测试在不同的进程中运行,静态缓存会在每次测试执行之间被清除,这有效地禁用了缓存机制。

The Spring TestContext framework stores application contexts in a static cache. This means that the context is literally stored in a static variable. In other words, if tests run in separate processes, the static cache is cleared between each test execution, which effectively disables the caching mechanism.

要从缓存机制中受益,所有测试都必须在同一进程或测试套件中运行。这可以通过在 IDE 中将所有测试作为一个组执行来实现。同样,在使用诸如 Ant、Maven 或 Gradle 的构建框架执行测试时,确保构建框架不会在测试之间分叉非常重要。例如,如果 Maven Surefire 插件的 forkMode设置为 always`或 `pertest,则 TestContext 框架无法在测试类之间缓存应用程序上下文,并且构建过程会因此慢得多。

To benefit from the caching mechanism, all tests must run within the same process or test suite. This can be achieved by executing all tests as a group within an IDE. Similarly, when executing tests with a build framework such as Ant, Maven, or Gradle, it is important to make sure that the build framework does not fork between tests. For example, if the forkMode for the Maven Surefire plug-in is set to always or pertest, the TestContext framework cannot cache application contexts between test classes, and the build process runs significantly more slowly as a result.

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

The size of the context cache is bounded with a default maximum size of 32. Whenever the maximum size is reached, a least recently used (LRU) eviction policy is used to evict and close stale contexts. You can configure the maximum size from the command line or a build script by setting a JVM system property named spring.test.context.cache.maxSize. As an alternative, you can set the same property via the SpringProperties mechanism.

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

Since having a large number of application contexts loaded within a given test suite can cause the suite to take an unnecessarily long time to run, it is often beneficial to know exactly how many contexts have been loaded and cached. To view the statistics for the underlying context cache, you can set the log level for the org.springframework.test.context.cache logging category to DEBUG.

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

In the unlikely case that a test corrupts the application context and requires reloading (for example, by modifying a bean definition or the state of an application object), you can annotate your test class or test method with @DirtiesContext (see the discussion of @DirtiesContext in Spring Testing Annotations ). This instructs Spring to remove the context from the cache and rebuild the application context before running the next test that requires the same application context. Note that support for the @DirtiesContext annotation is provided by the DirtiesContextBeforeModesTestExecutionListener and the DirtiesContextTestExecutionListener, which are enabled by default.

ApplicationContext lifecycle and console logging

当您需要调试使用 Spring TestContext Framework 执行的测试时,分析控制台输出(即,输出到 SYSOUTSYSERR 流)可能很有用。一些构建工具和 IDE 能够将控制台输出与给定的测试相关联;然而,一些控制台输出无法轻易与给定测试相关联。

When you need to debug a test executed with the Spring TestContext Framework, it can be useful to analyze the console output (that is, output to the SYSOUT and SYSERR streams). Some build tools and IDEs are able to associate console output with a given test; however, some console output cannot be easily associated with a given test.

关于由 Spring Framework 本身或在 ApplicationContext 中注册的组件触发的控制台日志记录,了解由 Spring TestContext Framework 在测试套件中加载的 ApplicationContext 的生命周期非常重要。

With regard to console logging triggered by the Spring Framework itself or by components registered in the ApplicationContext, it is important to understand the lifecycle of an ApplicationContext that has been loaded by the Spring TestContext Framework within a test suite.

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

The ApplicationContext for a test is typically loaded when an instance of the test class is being prepared — for example, to perform dependency injection into @Autowired fields of the test instance. This means that any console logging triggered during the initialization of the ApplicationContext typically cannot be associated with an individual test method. However, if the context is closed immediately before the execution of a test method according to @DirtiesContext semantics, a new instance of the context will be loaded just prior to execution of the test method. In the latter scenario, an IDE or build tool may potentially associate console logging with the individual test method.

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

The ApplicationContext for a test can be closed via one of the following scenarios.

  • The context is closed according to @DirtiesContext semantics.

  • The context is closed because it has been automatically evicted from the cache according to the LRU eviction policy.

  • The context is closed via a JVM shutdown hook when the JVM for the test suite terminates.

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

If the context is closed according to @DirtiesContext semantics after a particular test method, an IDE or build tool may potentially associate console logging with the individual test method. If the context is closed according to @DirtiesContext semantics after a test class, any console logging triggered during the shutdown of the ApplicationContext cannot be associated with an individual test method. Similarly, any console logging triggered during the shutdown phase via a JVM shutdown hook cannot be associated with an individual test method.

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

When a Spring ApplicationContext is closed via a JVM shutdown hook, callbacks executed during the shutdown phase are executed on a thread named SpringContextShutdownHook. So, if you wish to disable console logging triggered when the ApplicationContext is closed via a JVM shutdown hook, you may be able to register a custom filter with your logging framework that allows you to ignore any logging initiated by that thread.