Java Spring测试上下文最佳实践
我试图用集成测试来涵盖一个巨大的Spring Boot应用程序。应用程序中有很多SpringBean。加载Spring上下文需要一段时间 所以我想知道-Java Spring测试上下文最佳实践,java,spring,design-patterns,spring-boot,spring-test,Java,Spring,Design Patterns,Spring Boot,Spring Test,我试图用集成测试来涵盖一个巨大的Spring Boot应用程序。应用程序中有很多SpringBean。加载Spring上下文需要一段时间 所以我想知道- Spring是否足够聪明,能够在位于不同类中的多个集成测试之间共享相同的上下文?我的意思是避免为每个测试类初始化重载上下文 当测试1,2,4使用TestContextOne而测试3,5使用TestContextTwo时会发生什么?Spring是否以1,2,4,3,5的顺序发布它们?还是Spring在内存中保留了两个上下文 p.S.换句话说,
- Spring是否足够聪明,能够在位于不同类中的多个集成测试之间共享相同的上下文?我的意思是避免为每个测试类初始化重载上下文
- 当测试1,2,4使用
而测试3,5使用TestContextOne
时会发生什么?Spring是否以1,2,4,3,5的顺序发布它们?还是Spring在内存中保留了两个上下文TestContextTwo
p.S.换句话说,在所有集成测试中使用单个“完整”Spring上下文是常见的做法,spring framework为测试应用程序提供的一个主要特性是上下文缓存机制,以避免您提到的负载开销,而不是为每个测试编写单独的缓存。spring文档说明: TestContext框架为测试加载
ApplicationContext
(或WebApplicationContext
)后,该上下文将被缓存并重新用于在同一测试套件中声明相同唯一上下文配置的所有后续测试
考虑到这一点,您必须了解缓存机制是如何工作的,以确定构建测试的最佳策略。这里的问题是:当spring缓存上下文时,它使用什么键将上下文存储在内存中?
。根据文档,密钥基于容器的一些参数:
ApplicationContext
可以通过用于加载它的配置参数组合来唯一标识。因此,配置参数的唯一组合用于生成一个键
,在该键下缓存上下文。TestContext框架使用以下配置参数来构建上下文缓存键:
位置
(来自@ContextConfiguration)类
(来自@ContextConfiguration)contextInitializerClass
(来自@ContextConfiguration)contextCustomizers
(来自ContextCustomizerFactory)contextLoader
(来自@ContextConfiguration)parent
(来自@ContextHierarchy)activeProfiles
(来自@activeProfiles)propertySourceLocations
(来自@TestPropertySource)propertySourceProperties
(来自@TestPropertySource)resourceBasePath
(来自@WebAppConfiguration)
基于这些信息,我可能建议您,最佳实践是以一种方式组织您的测试,即它们使用相同的上下文参数集(即相同的缓存键),以受益于缓存机制,并避免加载另一个上下文。Spring文档还提供了一个示例:
…,如果TestClassA
为@ContextConfiguration的位置(或值)属性指定{“app config.xml”,“test config.xml”}
,TestContext
框架将加载相应的ApplicationContext
并将其存储在静态上下文缓存中,该缓存位于仅基于这些位置的键下。因此,如果TestClassB
也为其位置定义了{“app config.xml”,“test config.xml”}
(通过继承显式或隐式定义),但没有定义@WebAppConfiguration
,则不同的上下文加载器
,不同的活动配置文件,不同的上下文初始化器,不同的测试属性源或不同的父上下文,那么两个测试类将共享相同的ApplicationContext
。这意味着加载应用程序上下文的设置成本只发生一次(每个测试套件),并且后续测试执行速度要快得多
您可以在集成测试中使用的另一个技巧是强制上下文中的所有bean“懒惰”。这在仅运行一个集成测试时非常有用,因为您不必等待整个应用程序上下文被加载和初始化。这可以显著缩短运行单个测试所需的时间 您可能会遇到隐式创建bean的情况(例如:SpringIntegrationFlow)。该流从未直接注入到任何对象中,但您的类可能引用了该流创建的bean。在这种情况下,您要么需要@Autowire您的流(以确保隐式bean被创建),要么可以使用BeanPostProcessor获得创造性 我创建了以下后处理器,您只需将其添加到测试spring上下文中即可
public class LazyInitBeanFactoryPostProcessor implements BeanFactoryPostProcessor {
private Class<?>[] exclusionList;
public LazyInitBeanFactoryPostProcessor() {
}
public LazyInitBeanFactoryPostProcessor(Class<?>[] exclusionList) {
this.exclusionList = exclusionList;
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
//Iterate over all bean, mark them as lazy if they are not in the exclusion list.
for (String beanName : beanFactory.getBeanDefinitionNames()) {
if (isLazy(beanName, beanFactory)) {
BeanDefinition definition = beanFactory.getBeanDefinition(beanName);
definition.setLazyInit(true);
}
}
}
private boolean isLazy(String beanName, ConfigurableListableBeanFactory beanFactory) {
if (exclusionList == null || exclusionList.length == 0) {
return true;
}
for (Class<?> clazz : exclusionList) {
if (beanFactory.isTypeMatch(beanName,clazz)) {
return false;
}
}
return true;
}
}
或者使用排除来扩展它(在本例中,任何可分配给Spring集成流的bean都不会被标记为懒惰:
@TestConfiguration
protected static class TestConfiguration {
@Bean
public BeanFactoryPostProcessor lazyBeanPostProcessor() {
return new ExtendedTestLazyBeanFactoryPostProcessor();
}
static private class ExtendedTestLazyBeanFactoryPostProcessor extends LazyInitBeanFactoryPostProcessor {
public ServiceTestLazyBeanFactoryPostProcessor() {
super(new Class<?>[] {IntegrationFlow.class});
}
}
@TestConfiguration
受保护的静态类TestConfiguration{
@豆子
公共BeanFactory后处理器lazyBeanPostProcessor(){
返回新的ExtendedTestLazyBeanFactoryPostProcessor();
}
静态私有类ExtendedTestLazyBeanFactoryPostProcessor扩展LazyInitBeanFactoryPostProcessor{
公共服务TestLazybeanFactory后处理器(){
super(新类[]{IntegrationFlow.Class});
}
}
如果你自己在理论上没有处理过奇怪的事情,那么它应该只加载2个上下文。@M.Deinum所以通常的做法是对所有集成测试使用一个“完整”上下文,对吗?这取决于你想要单元、集成还是系统测试。你完全可以编写一个集成测试(针对单个组件)然后只引导该组件(及其依赖项)进行系统测试时,您希望整个系统正常运行。@M.Deinum是的,每个测试只涉及很少的组件。但是我担心,设置上百个不同的小测试上下文是非常困难的
@TestConfiguration
protected static class TestConfiguration {
@Bean
public BeanFactoryPostProcessor lazyBeanPostProcessor() {
return new ExtendedTestLazyBeanFactoryPostProcessor();
}
static private class ExtendedTestLazyBeanFactoryPostProcessor extends LazyInitBeanFactoryPostProcessor {
public ServiceTestLazyBeanFactoryPostProcessor() {
super(new Class<?>[] {IntegrationFlow.class});
}
}