Spring测试环境最佳实践

时间:2017-03-20 19:43:41

标签: java spring design-patterns spring-boot spring-test

我试图用集成测试覆盖一个巨大的Spring Boot应用程序。应用程序中有很多Spring bean。加载Spring上下文需要一段时间。

所以我想知道 -

  • Spring是否足够聪明,可以在位于不同类中的多个集成测试之间共享相同的上下文?我的意思是避免为每个测试类初始化重量级上下文。
  • 当测试1,2,4使用TestContextOne并测试3,5使用TestContextTwo时会发生什么? Spring会以1,2,4,3,5的顺序启动它们吗?或者Spring会在内存中保留两个上下文吗?

P.S。 换句话说,是使用单个" full"的常见做法。所有集成测试的Spring Context,而不是为每个测试编写单独的测试?

2 个答案:

答案 0 :(得分:10)

Spring框架提供的用于测试应用程序的主要功能之一是上下文缓存机制,以避免您提及的负载开销。春季文档说:

  

一旦TestContext框架为测试加载ApplicationContext(或WebApplicationContext),该上下文将被缓存并重用于在同一测试套件中声明相同唯一上下文配置的所有后续测试。

考虑到这一点,您必须了解缓存机制如何确定构建测试的最佳策略。这里的问题是:When spring caches the context, it stores this context in memory using what key?。根据文档,密钥是基于容器的一些参数:

  

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

     

locations(来自@ContextConfiguration)
  classes(来自@ContextConfiguration)
  contextInitializerClasses(来自@ContextConfiguration)
  contextCustomizers(来自ContextCustomizerFactory)
  contextLoader(来自@ContextConfiguration)
  parent(来自@ContextHierarchy)
  activeProfiles(来自@ActiveProfiles)
  propertySourceLocations(来自@TestPropertySource)
  propertySourceProperties(来自@TestPropertySource)
  resourceBasePath(来自@WebAppConfiguration)

基于这些信息,我可能会建议您最好的做法是以一种使用相同的上下文参数集(即相同的缓存键)的方式组织您的测试,以便从缓存机制中受益,并避免使用另一个上下文加载。 Spring文档也给出了一个例子:

  

...,如果TestClassA指定了{" app-config.xml"," test-config.xml"}的位置(或值)属性@ContextConfiguration,TestContext框架将加载相应的ApplicationContext并将其存储在仅基于这些位置的键下的静态上下文缓存中。因此,如果TestClassB还为其位置定义{" app-config.xml"," test-config.xml"}(通过继承显式或隐式),但不定义@WebAppConfiguration,不同的ContextLoader,不同的活动配置文件,不同的上下文初始化器,不同的测试属性源或不同的父上下文,然后两个测试类共享相同的ApplicationContext 。这意味着加载应用程序上下文的设置成本只发生一次(每个测试套件),后续测试执行速度要快得多。

答案 1 :(得分:2)

您可以在集成测试中使用的另一个技巧是强制上下文中的所有bean都“懒惰”。这在运行一个集成测试时非常有用,因为您不必等待加载和初始化整个应用程序上下文。这可以显着缩短运行单个测试所需的时间。

您可能会遇到隐式创建bean的情况(例如:Spring IntegrationFlow)。流不会直接注入任何东西,但是您的类可能引用了流创建的bean。在这种情况下,您需要@Autowire您的流程(以确保隐式bean被创建)或者您可以使用BeanPostProcessor获得创意。

我创建了以下后期处理器,您只需将其添加到测试弹簧上下文中。

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;        
    }
}

使用它:

@SpringBootTest(webEnvironment = WebEnvironment.NONE)
public class MyTest {
.
.
.
@TestConfiguration
protected static class TestConfiguration {

    @Bean
    public BeanFactoryPostProcessor lazyBeanPostProcessor() {
        return new LazyInitBeanFactoryPostProcessor();
    }
}

或者使用排除项扩展它(在此示例中,任何可分配给Spring Integration流的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});
        }   
    }