3.5Spring TestContext框架

Spring TestContext Framework(位于org.springframework.test.context包中)提供了通用的,注解驱动的单元和集成测试支持,它与使用中的测试框架无关。 TestContext框架也非常重视约定优于配置,合理的默认值可以通过基于注解的配置覆盖。

除了通用测试基础结构之外,TestContext框架还为JUnit 4,JUnit Jupiter(AKA JUnit 5)和TestNG提供了显式支持。对于JUnit 4和TestNG,Spring提供了抽象支持类。此外,Spring为JUnit 4提供了一个自定义JUnit Runner和自定义JUnit规则,并为JUnit Jupiter提供了一个自定义扩展,可以编写所谓的POJO测试类。 POJO测试类不需要扩展特定的类层次结构,例如抽象支持类。

以下部分概述了TestContext框架的内部结构。如果你只对使用框架感兴趣并且不想使用自己的自定义侦听器或自定义加载器扩展它,请随意直接进入配置(上下文管理,依赖注入,事务管理),支持类和注解支持部分。

3.5.1 关键抽象

框架的核心包括TestContextManager类和TestContext,TestExecutionListener和SmartContextLoader接口。 为每个测试类创建一个TestContextManager(例如,用于在JUnit Jupiter中的单个测试类中执行所有测试方法)。 反过来,TestContextManager管理一个包含当前测试上下文的TestContext。 TestContextManager还会在测试进行时更新TestContext的状态,并委托给TestExecutionListener实现,这些实现通过提供依赖注入,管理事务等来检测实际的测试执行。 SmartContextLoader负责为给定的测试类加载ApplicationContext。 有关各种实现的更多信息和示例,请参阅javadoc和Spring测试套件。

TestContext

TestContext封装了执行测试的上下文(与使用中的实际测试框架无关),并为其负责的测试实例提供上下文管理和缓存支持。 TestContext还委托SmartContextLoader在需要时加载ApplicationContext。

TestContextManager

TestContextManager是Spring TestContext Framework的主要入口点,负责管理单个TestContext并在明确定义的测试执行点向每个注册的TestExecutionListener发送信号事件:

  • 在特定测试框架的任何“before class”或“before all”方法之前。

  • 测试实例后处理。

  • 在特定测试框架的任何“before”或“before each”方法之前。

  • 在执行测试方法之前,但在测试设置之后。

  • 在执行测试方法之后但在测试之前立即拆除。

  • 在特定测试框架的任何“after”或“after each”方法之后。

  • 在特定测试框架的任何“after class”或“after all”方法之后。

TestExecutionListener

TestExecutionListener定义API,以响应由注册侦听器的TestContextManager发布的测试执行事件。 请参阅TestExecutionListener配置。

Context Loaders

ContextLoader是一个策略接口,用于为Spring TestContext Framework管理的集成测试加载ApplicationContext。你应该实现SmartContextLoader而不是此接口,以提供对带注解的类,活动Bean定义配置文件,测试属性源,上下文层次结构和WebApplicationContext支持的支持。

SmartContextLoader是Spring 3.1中引入的ContextLoader接口的扩展,取代了原始的最小ContextLoader SPI。具体来说,SmartContextLoader可以选择处理资源位置,带注解的类或上下文初始值设定项。此外,SmartContextLoader可以在其加载的上下文中设置活动Bean定义概要文件和测试属性源。

Spring提供以下实现:

  • DelegatingSmartContextLoader:两个默认加载器之一,它内部委托给AnnotationConfigContextLoader,GenericXmlContextLoader或GenericGroovyXmlContextLoader,具体取决于为测试类声明的配置或默认位置或默认配置类的存在。仅当Groovy位于类路径上时才启用Groovy支持。

  • WebDelegatingSmartContextLoader:两个默认加载器之一,它内部委托给AnnotationConfigWebContextLoader,GenericXmlWebContextLoader或GenericGroovyXmlWebContextLoader,具体取决于为测试类声明的配置或默认位置或默认配置类的存在。仅当测试类中存在@WebAppConfiguration时,才使用Web ContextLoader。仅当Groovy位于类路径上时才启用Groovy支持。

  • AnnotationConfigContextLoader:从带注解的类中加载标准ApplicationContext。

  • AnnotationConfigWebContextLoader:从带注解的类中加载WebApplicationContext。

  • GenericGroovyXmlContextLoader:从Groovy脚本或XML配置文件的资源位置加载标准ApplicationContext。

  • GenericGroovyXmlWebContextLoader:从Groovy脚本或XML配置文件的资源位置加载WebApplicationContext。

  • GenericXmlContextLoader:从XML资源位置加载标准ApplicationContext。

  • GenericXmlWebContextLoader:从XML资源位置加载WebApplicationContext。

  • GenericPropertiesContextLoader:从Java属性文件加载标准ApplicationContext。

3.5.2 引导TestContext框架

Spring TestContext Framework内部的默认配置足以满足所有常见用例。但是,有时开发团队或第三方框架要更改默认的ContextLoader,实现自定义TestContext或ContextCache,扩充ContextCustomizerFactory和TestExecutionListener实现的默认集,依此类推。对于TestContext框架如何操作的这种低级控制,Spring提供了一个引导策略。

TestContextBootstrapper定义了用于引导TestContext框架的SPI。 TestContextManager使用TestContextBootstrapper为当前测试加载TestExecutionListener实现并构建它管理的TestContext。你可以使用@BootstrapWith直接或作为元注解为测试类(或测试类层次结构)配置自定义引导策略。如果未使用@BootstrapWith显式配置引导程序,则使用DefaultTestContextBootstrapper或WebTestContextBootstrapper,具体取决于@WebAppConfiguration的存在。

由于TestContextBootstrapper SPI将来可能会发生变化(以适应新的需求),我们强烈建议实施者不要直接实现此接口,而是扩展AbstractTestContextBootstrapper或其中一个具体的子类。

3.5.3 TestExecutionListener 配置

Spring提供了以下默认注册的TestExecutionListener实现,完全按以下顺序:

  • ServletTestExecutionListener:为WebApplicationContext配置Servlet API模拟。

  • DirtiesContextBeforeModesTestExecutionListener:处理“之前”模式的@DirtiesContext注解。

  • DependencyInjectionTestExecutionListener:为测试实例提供依赖项注入。

  • DirtiesContextTestExecutionListener:处理“after”模式的@DirtiesContext注解。

  • TransactionalTestExecutionListener:使用默认回滚语义提供事务测试执行。

  • SqlScriptsTestExecutionListener:运行使用@Sql批注配置的SQL脚本。

注册自定义TestExecutionListener实现

你可以使用@TestExecutionListeners批注为测试类及其子类注册自定义TestExecutionListener实现。有关详细信息和示例,请参阅注解支持和@TestExecutionListeners的javadoc。

自动发现默认的TestExecutionListener实现

使用@TestExecutionListeners注册自定义TestExecutionListener实现适用于在有限测试场景中使用的自定义侦听器。但是,如果需要在测试套件中使用自定义侦听器,则会变得很麻烦。从Spring Framework 4.1开始,通过支持通过SpringFactoriesLoader机制自动发现默认的TestExecutionListener实现来解决此问题。

具体来说,spring-test模块在其META-INF/spring.factories属性文件中的org.springframework.test.context.TestExecutionListener项下声明所有核心默认的TestExecutionListener`实现。第三方框架和开发人员可以通过自己的META-INF/spring.factories属性文件以相同的方式将自己的TestExecutionListener实现提供给默认侦听器列表。

订购TestExecutionListener实现

当TestContext框架通过前面提到的SpringFactoriesLoader机制发现默认的TestExecutionListener实现时,实例化的侦听器通过使用Spring的AnnotationAwareOrderComparator进行排序,它遵循Spring的Ordered接口和@Order注解进行排序。 AbstractTestExecutionListener和Spring提供的所有默认TestExecutionListener实现都使用适当的值进行Ordered。因此,第三方框架和开发人员应确保通过实现Ordered或声明@Order以正确的顺序注册其默认的TestExecutionListener实现。有关为每个核心侦听器分配的值的详细信息,请参阅javadoc以获取核心默认TestExecutionListener实现的getOrder()方法。

合并TestExecutionListener实现

如果通过@TestExecutionListeners注册了自定义TestExecutionListener,则不会注册默认侦听器。在大多数常见的测试场景中,除了任何自定义侦听器之外,这还有效地迫使开发人员手动声明所有默认侦听器。以下清单演示了这种配置风格:

@ContextConfiguration
@TestExecutionListeners({
    MyCustomTestExecutionListener.class,
    ServletTestExecutionListener.class,
    DirtiesContextBeforeModesTestExecutionListener.class,
    DependencyInjectionTestExecutionListener.class,
    DirtiesContextTestExecutionListener.class,
    TransactionalTestExecutionListener.class,
    SqlScriptsTestExecutionListener.class
})
public class MyTest {
    // class body...
}

这种方法的挑战在于它要求开发人员确切地知道默认情况下注册了哪些监听器。此外,默认侦听器集可以在发行版之间更改 - 例如,Spring Framework 4.1中引入了SqlScriptsTestExecutionListener,而Spring Framework 4.2中引入了DirtiesContextBeforeModesTestExecutionListener。此外,Spring Security等第三方框架通过使用上述自动发现机制注册了自己的默认TestExecutionListener实现。

为避免必须知道并重新声明所有默认侦听器,可以将@TestExecutionListeners的mergeMode属性设置为MergeMode.MERGE_WITH_DEFAULTS。 MERGE_WITH_DEFAULTS指示本地声明的侦听器应与默认侦听器合并。合并算法确保从列表中删除重复项,并根据AnnotationAwareOrderComparator的语义对生成的合并侦听器集进行排序,如Ordering TestExecutionListener实现中所述。如果侦听器实现Ordered或使用@Order注解,它可以影响它与默认值合并的位置。否则,在合并时,本地声明的侦听器将附加到默认侦听器列表中。

例如,如果前一个示例中的MyCustomTestExecutionListener类将其顺序值(例如,500)配置为小于ServletTestExecutionListener(恰好为1000)的顺序,则MyCustomTestExecutionListener可以自动与默认值列表合并在ServletTestExecutionListener前面,前面的示例可以替换为以下内容:

@ContextConfiguration
@TestExecutionListeners(
    listeners = MyCustomTestExecutionListener.class,
    mergeMode = MERGE_WITH_DEFAULTS
)
public class MyTest {
    // class body...
}

3.5.4 Context 管理

每个TestContext都为其负责的测试实例提供上下文管理和缓存支持。 测试实例不会自动接收对已配置的ApplicationContext的访问权限。 但是,如果测试类实现ApplicationContextAware接口,则会向测试实例提供对ApplicationContext的引用。 请注意,AbstractJUnit4SpringContextTests和AbstractTestNGSpringContextTests实现ApplicationContextAware,因此可以自动提供对ApplicationContext的访问。

@Autowired ApplicationContext

作为实现ApplicationContextAware接口的替代方法,你可以通过字段或setter方法上的@Autowired注解为测试类注入应用程序上下文,如以下示例所示:

@RunWith(SpringRunner.class)
@ContextConfiguration
public class MyTest {

    @Autowired 
    private ApplicationContext applicationContext;

    // class body...
}

同样,如果你的测试配置为加载WebApplicationContext,则可以将Web应用程序上下文注入到测试中,如下所示:

@RunWith(SpringRunner.class)
@WebAppConfiguration 
@ContextConfiguration
public class MyWebAppTest {

    @Autowired 
    private WebApplicationContext wac;

    // class body...
}

使用@Autowired的依赖注入是由DependencyInjectionTestExecutionListener提供的,默认情况下配置它(参见测试夹具的依赖注入)。

使用TestContext框架的测试类不需要扩展任何特定类或实现特定接口来配置其应用程序上下文。相反,通过在类级别声明@ContextConfiguration批注来实现配置。如果测试类未显式声明应用程序上下文资源位置或带注解的类,则配置的ContextLoader将确定如何从默认位置或默认配置类加载上下文。除了上下文资源位置和带注解的类之外,还可以通过应用程序上下文初始化程序配置应用程序上下文。

以下部分说明如何使用Spring的@ContextConfiguration批注通过使用XML配置文件,Groovy脚本,带注解的类(通常为@Configuration类)或上下文初始化程序来配置测试ApplicationContext。或者,你可以为高级用例实现和配置自己的自定义SmartContextLoader。

Context Configuration with XML resources

要使用XML配置文件为测试加载ApplicationContext,请使用@ContextConfiguration注解测试类,并使用包含XML配置元数据的资源位置的数组配置locations属性。 普通路径或相对路径(例如,context.xml)被视为相对于定义测试类的包的类路径资源。 以斜杠开头的路径被视为绝对类路径位置(例如,/ org / example / config.xml)。 表示资源URL的路径(即,以classpath:,file:,http:等为前缀的路径)按原样使用。

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/app-config.xml" and
// "/test-config.xml" in the root of the classpath
@ContextConfiguration(locations={"/app-config.xml", "/test-config.xml"}) 
public class MyTest {
    // class body...
}

@ContextConfiguration通过标准Java值属性支持locations属性的别名。 因此,如果你不需要在@ContextConfiguration中声明其他属性,则可以省略位置属性名称的声明,并使用以下示例中演示的缩写格式声明资源位置:

@RunWith(SpringRunner.class)
@ContextConfiguration({"/app-config.xml", "/test-config.xml"}) 
public class MyTest {
    // class body...
}

如果省略@ContextConfiguration批注中的位置和值属性,则TestContext框架会尝试检测默认的XML资源位置。 具体来说,GenericXmlContextLoader和GenericXmlWebContextLoader根据测试类的名称检测默认位置。 如果你的类名为com.example.MyTest,则GenericXmlContextLoader将从“classpath:com / example / MyTest-context.xml”加载你的应用程序上下文。 以下示例显示了如何执行此操作:

package com.example;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTest-context.xml"
@ContextConfiguration 
public class MyTest {
    // class body...
}

Context Configuration with Groovy Scripts

要使用使用Groovy Bean Definition DSL的Groovy脚本为测试加载ApplicationContext,可以使用@ContextConfiguration注解测试类,并使用包含Groovy脚本资源位置的数组配置locations或value属性。 Groovy脚本的资源查找语义与XML配置文件中描述的相同。

启用Groovy脚本支持

如果Groovy在类路径上,则支持使用Groovy脚本在Spring TestContext Framework中加载ApplicationContext。

以下示例显示如何指定Groovy配置文件:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/AppConfig.groovy" and
// "/TestConfig.groovy" in the root of the classpath
@ContextConfiguration({"/AppConfig.groovy", "/TestConfig.Groovy"}) 
public class MyTest {
    // class body...
}

如果省略@ContextConfiguration批注中的位置和值属性,TestContext框架将尝试检测默认的Groovy脚本。 具体来说,GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader根据测试类的名称检测默认位置。 如果你的类名为com.example.MyTest,则Groovy上下文加载器将从“classpath:com / example / MyTestContext.groovy”加载你的应用程序上下文。 以下示例显示了如何使用默认值:

package com.example;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "classpath:com/example/MyTestContext.groovy"
@ContextConfiguration 
public class MyTest {
    // class body...
}

同时声明XML配置和Groovy脚本

你可以使用@ContextConfiguration的locations或value属性同时声明XML配置文件和Groovy脚本。 如果配置的资源位置的路径以.xml结尾,则使用XmlBeanDefinitionReader加载它。 否则,使用GroovyBeanDefinitionReader加载它。

以下清单显示了如何在集成测试中将两者结合使用:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from
// "/app-config.xml" and "/TestConfig.groovy"
@ContextConfiguration({ "/app-config.xml", "/TestConfig.groovy" })
public class MyTest {
    // class body...
}

Context Configuration with Annotated Classes

要使用带注解的类为测试加载ApplicationContext(请参阅基于Java的容器配置),可以使用@ContextConfiguration注解测试类,并使用包含对带注解类的引用的数组配置classes属性。 以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from AppConfig and TestConfig
@ContextConfiguration(classes = {AppConfig.class, TestConfig.class}) 
public class MyTest {
    // class body...
}

如果省略@ContextConfiguration批注中的classes属性,TestContext框架将尝试检测是否存在默认配置类。 具体来说,AnnotationConfigContextLoader和AnnotationConfigWebContextLoader将检测满足配置类实现要求的测试类的所有静态嵌套类,如@Configuration javadoc中所指定。 请注意,配置类的名称是任意的。 此外,如果需要,测试类可以包含多个静态嵌套配置类。 在以下示例中,OrderServiceTest类声明一个名为Config的静态嵌套配置类,该类自动用于加载测试类的ApplicationContext:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from the
// static nested Config class
@ContextConfiguration 
public class OrderServiceTest {

    @Configuration
    static class Config {

        // this bean will be injected into the OrderServiceTest class
        @Bean
        public OrderService orderService() {
            OrderService orderService = new OrderServiceImpl();
            // set properties, etc.
            return orderService;
        }
    }

    @Autowired
    private OrderService orderService;

    @Test
    public void testOrderService() {
        // test the orderService
    }

}

Mixing XML, Groovy Scripts, and Annotated Classes

有时可能需要混合XML配置文件,Groovy脚本和带注解的类(通常为@Configuration类)来为测试配置ApplicationContext。例如,如果在生产中使用XML配置,则可能决定要使用@Configuration类为测试配置特定的Spring管理组件,反之亦然。

此外,一些第三方框架(例如Spring Boot)为同时从不同类型的资源加载ApplicationContext提供了一流的支持(例如,XML配置文件,Groovy脚本和@Configuration类)。历史上,Spring Framework并未支持标准部署。因此,Spring Framework在spring-test模块中提供的大多数SmartContextLoader实现仅支持每个测试上下文的一种资源类型。但是,这并不意味着你不能同时使用两者。一般规则的一个例外是GenericGroovyXmlContextLoader和GenericGroovyXmlWebContextLoader同时支持XML配置文件和Groovy脚本。此外,第三方框架可以选择通过@ContextConfiguration支持位置和类的声明,并且,通过TestContext框架中的标准测试支持,你可以使用以下选项。

如果要使用资源位置(例如,XML或Groovy)和@Configuration类来配置测试,则必须选择一个作为入口点,并且必须包含或导入另一个。例如,在XML或Groovy脚本中,你可以通过使用组件扫描包含@Configuration类或将它们定义为普通的Spring bean,而在@Configuration类中,你可以使用@ImportResource来导入XML配置文件或Groovy脚本。请注意,此行为在语义上等同于你在生产中配置应用程序的方式:在生产配置中,你可以定义一组XML或Groovy资源位置或一组@Configuration类,从中加载生产ApplicationContext,但你仍然拥有自由包含或导入其他类型的配置。

Context Configuration with Context Initializers

要使用上下文初始化程序为测试配置ApplicationContext,请使用@ContextConfiguration注解测试类,并使用包含对实现ApplicationContextInitializer的类的引用的数组配置initializers属性。 然后使用声明的上下文初始值设定项初始化为测试加载的ConfigurableApplicationContext。 请注意,每个声明的初始化程序支持的具体ConfigurableApplicationContext类型必须与正在使用的SmartContextLoader(通常是GenericApplicationContext)创建的ApplicationContext类型兼容。 此外,调用初始值设定项的顺序取决于它们是实现Spring的Ordered接口还是使用Spring的@Order注解或标准@Priority注解进行注解。 以下示例显示了如何使用初始值设定项:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from TestConfig
// and initialized by TestAppCtxInitializer
@ContextConfiguration(
    classes = TestConfig.class,
    initializers = TestAppCtxInitializer.class) 
public class MyTest {
    // class body...
}

你还可以完全省略@ContextConfiguration中的XML配置文件,Groovy脚本或带注解的类的声明,而是仅声明ApplicationContextInitializer类,然后它们负责在上下文中注册bean - 例如,通过以编程方式从XML文件加载bean定义 或配置类。 以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by EntireAppInitializer
// which presumably registers beans in the context
@ContextConfiguration(initializers = EntireAppInitializer.class) 
public class MyTest {
    // class body...
}

Context Configuration Inheritance

@ContextConfiguration支持boolean inheritLocations和inheritInitializers属性,这些属性表示是否应继承超类声明的资源位置或带注解的类和上下文初始值设定项。两个标志的默认值均为true。这意味着测试类继承资源位置或带注解的类以及任何超类声明的上下文初始值设定项。具体而言,测试类的资源位置或带注解的类将附加到超类声明的资源位置列表或带注解的类。类似地,给定测试类的初始值设定项被添加到由测试超类定义的初始化程序集中。因此,子类可以选择扩展资源位置,带注解的类或上下文初始化器。

如果@ContextConfiguration中的inheritLocations或inheritInitializers属性设置为false,则资源位置或带注解的类和上下文初始化器分别为测试类阴影并有效地替换超类定义的配置。

在下一个使用XML资源位置的示例中,ExtendedTest的ApplicationContext按顺序从base-config.xml和extended-config.xml加载。因此,在extended-config.xml中定义的Bean可以覆盖(即替换)base-config.xml中定义的bean。以下示例显示了一个类如何扩展另一个类并使用其自己的配置文件和超类的配置文件:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "/base-config.xml"
// in the root of the classpath
@ContextConfiguration("/base-config.xml") 
public class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from "/base-config.xml" and
// "/extended-config.xml" in the root of the classpath
@ContextConfiguration("/extended-config.xml") 
public class ExtendedTest extends BaseTest {
    // class body...
}

类似地,在下一个使用带注解的类的示例中,ExtendedTest的ApplicationContext按顺序从BaseConfig和ExtendedConfig类加载。 因此,在ExtendedConfig中定义的Bean可以覆盖(即替换)BaseConfig中定义的Bean。 以下示例显示了一个类如何扩展另一个类并使用它自己的配置类和超类的配置类:

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from BaseConfig
@ContextConfiguration(classes = BaseConfig.class) 
public class BaseTest {
    // class body...
}

// ApplicationContext will be loaded from BaseConfig and ExtendedConfig
@ContextConfiguration(classes = ExtendedConfig.class) 
public class ExtendedTest extends BaseTest {
    // class body...
}

在下一个使用上下文初始化器的示例中,使用BaseInitializer和ExtendedInitializer初始化ExtendedTest的ApplicationContext。 但请注意,调用初始值设定项的顺序取决于它们是实现Spring的Ordered接口还是使用Spring的@Order注解或标准@Priority注解进行注解。 以下示例显示了一个类如何扩展另一个类并使用它自己的初始化程序和超类的初始化程序:

@RunWith(SpringRunner.class)
// ApplicationContext will be initialized by BaseInitializer
@ContextConfiguration(initializers = BaseInitializer.class) 
public class BaseTest {
    // class body...
}

// ApplicationContext will be initialized by BaseInitializer
// and ExtendedInitializer
@ContextConfiguration(initializers = ExtendedInitializer.class) 
public class ExtendedTest extends BaseTest {
    // class body...
}

Context Configuration with Environment Profiles

Spring 3.1在框架中引入了环境和配置文件概念(AKA“bean定义配置文件”)的一流支持,并且可以配置集成测试以激活各种测试场景的特定bean定义配置文件。 这是通过使用@ActiveProfiles批注对测试类进行批注并提供在为测试加载ApplicationContext时应激活的配置文件列表来实现的。

你可以将@ActiveProfiles与新SmartContextLoader SPI的任何实现一起使用,但旧版ContextLoader SPI的实现不支持@ActiveProfiles。

考虑XML配置和@Configuration类的两个示例:

<!-- app-config.xml -->
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jdbc="http://www.springframework.org/schema/jdbc"
    xmlns:jee="http://www.springframework.org/schema/jee"
    xsi:schemaLocation="...">

    <bean id="transferService"
            class="com.bank.service.internal.DefaultTransferService">
        <constructor-arg ref="accountRepository"/>
        <constructor-arg ref="feePolicy"/>
    </bean>

    <bean id="accountRepository"
            class="com.bank.repository.internal.JdbcAccountRepository">
        <constructor-arg ref="dataSource"/>
    </bean>

    <bean id="feePolicy"
        class="com.bank.service.internal.ZeroFeePolicy"/>

    <beans profile="dev">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
            <jdbc:script
                location="classpath:com/bank/config/sql/test-data.sql"/>
        </jdbc:embedded-database>
    </beans>

    <beans profile="production">
        <jee:jndi-lookup id="dataSource" jndi-name="java:comp/env/jdbc/datasource"/>
    </beans>

    <beans profile="default">
        <jdbc:embedded-database id="dataSource">
            <jdbc:script
                location="classpath:com/bank/config/sql/schema.sql"/>
        </jdbc:embedded-database>
    </beans>

</beans>
package com.bank.service;

@RunWith(SpringRunner.class)
// ApplicationContext will be loaded from "classpath:/app-config.xml"
@ContextConfiguration("/app-config.xml")
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // test the transferService
    }
}

运行TransferServiceTest时,将从类路径根目录中的app-config.xml配置文件加载其ApplicationContext。如果检查app-config.xml,则可以看到accountRepository bean依赖于dataSource bean。但是,dataSource未定义为顶级bean。相反,dataSource定义了三次:在生产配置文件中,在dev配置文件中,以及在默认配置文件中。

通过使用@ActiveProfiles(“dev”)注解TransferServiceTest,我们指示Spring TestContext Framework加载ApplicationContext,并将活动配置文件设置为{“dev”}。因此,创建嵌入式数据库并使用测试数据填充,并且accountRepository bean连接到对开发DataSource的引用。这可能是我们在集成测试中想要的。

将bean分配给默认配置文件有时很有用。仅当没有专门激活其他配置文件时,才会包含默认配置文件中的Bean。你可以使用它来定义要在应用程序的默认状态中使用的“回退”bean。例如,你可以显式为开发和生产配置文件提供数据源,但在这些数据源都不活动时将其定义为默认值。

以下代码清单演示了如何使用@Configuration类而不是XML实现相同的配置和集成测试:

@Configuration
@Profile("dev")
public class StandaloneDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .addScript("classpath:com/bank/config/sql/test-data.sql")
            .build();
    }
}
@Configuration
@Profile("production")
public class JndiDataConfig {

    @Bean(destroyMethod="")
    public DataSource dataSource() throws Exception {
        Context ctx = new InitialContext();
        return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
    }
}
@Configuration
@Profile("default")
public class DefaultDataConfig {

    @Bean
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
            .setType(EmbeddedDatabaseType.HSQL)
            .addScript("classpath:com/bank/config/sql/schema.sql")
            .build();
    }
}
@Configuration
public class TransferServiceConfig {

    @Autowired DataSource dataSource;

    @Bean
    public TransferService transferService() {
        return new DefaultTransferService(accountRepository(), feePolicy());
    }

    @Bean
    public AccountRepository accountRepository() {
        return new JdbcAccountRepository(dataSource);
    }

    @Bean
    public FeePolicy feePolicy() {
        return new ZeroFeePolicy();
    }

}
package com.bank.service;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public class TransferServiceTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // test the transferService
    }
}

在此变体中,我们将XML配置拆分为四个独立的@Configuration类:

  • TransferServiceConfig:使用@Autowired通过依赖注入获取dataSource。

  • StandaloneDataConfig:为适合开发人员测试的嵌入式数据库定义dataSource。

  • JndiDataConfig:定义在生产环境中从JNDI检索的dataSource。

  • DefaultDataConfig:如果没有配置文件处于活动状态,则为默认嵌入式数据库定义dataSource。

与基于XML的配置示例一样,我们仍然使用@ActiveProfiles(“dev”)注解TransferServiceTest,但这次我们使用@ContextConfiguration批注指定所有四个配置类。测试类的主体本身保持完全不变。

通常情况下,在给定项目中的多个测试类中使用单组配置文件。因此,为了避免@ActiveProfiles注解的重复声明,你可以在基类上声明一次@ActiveProfiles,并且子类自动从基类继承@ActiveProfiles配置。在下面的示例中,@ActiveProfiles(以及其他注解)的声明已移至抽象超类AbstractIntegrationTest:

package com.bank.service;

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = {
        TransferServiceConfig.class,
        StandaloneDataConfig.class,
        JndiDataConfig.class,
        DefaultDataConfig.class})
@ActiveProfiles("dev")
public abstract class AbstractIntegrationTest {
}
package com.bank.service;

// "dev" profile inherited from superclass
public class TransferServiceTest extends AbstractIntegrationTest {

    @Autowired
    private TransferService transferService;

    @Test
    public void testTransferService() {
        // test the transferService
    }
}

@ActiveProfiles还支持inheritProfiles属性,该属性可用于禁用活动配置文件的继承,如以下示例所示:

package com.bank.service;

// "dev" profile overridden with "production"
@ActiveProfiles(profiles = "production", inheritProfiles = false)
public class ProductionTransferServiceTest extends AbstractIntegrationTest {
    // test body
}

此外,有时需要以编程方式而不是声明性地解析测试的活动配置文件 - 例如,基于:

  • 目前的操作系统。

  • 是否在持续集成构建服务器上执行测试。

  • 存在某些环境变量。

  • 存在自定义类级注解。

  • 其他问题。

要以编程方式解析活动Bean定义概要文件,可以实现自定义ActiveProfilesResolver并使用@ActiveProfiles的resolver属性进行注册。 有关详细信息,请参阅相应的javadoc。 以下示例演示如何实现和注册自定义OperatingSystemActiveProfilesResolver:

package com.bank.service;

// "dev" profile overridden programmatically via a custom resolver
@ActiveProfiles(
    resolver = OperatingSystemActiveProfilesResolver.class,
    inheritProfiles = false)
public class TransferServiceTest extends AbstractIntegrationTest {
    // test body
}
package com.bank.service.test;

public class OperatingSystemActiveProfilesResolver implements ActiveProfilesResolver {

    @Override
    String[] resolve(Class<?> testClass) {
        String profile = ...;
        // determine the value of profile based on the operating system
        return new String[] {profile};
    }
}

Context Configuration with Test Property Sources

Spring 3.1在框架中引入了具有属性源层次结构的环境概念的一流支持。 从Spring 4.1开始,你可以使用特定于测试的属性源配置集成测试。 与@Configuration类上使用的@PropertySource注解相比,你可以在测试类上声明@TestPropertySource注解,以声明测试属性文件或内联属性的资源位置。 这些测试属性源被添加到环境中的PropertySource集合中,用于为带注解的集成测试加载的ApplicationContext。

你可以将@TestPropertySource与SmartContextLoader SPI的任何实现一起使用,但旧版ContextLoader SPI的实现不支持@TestPropertySource。

SmartContextLoader的实现通过MergedContextConfiguration中的getPropertySourceLocations()和getPropertySourceProperties()方法获得对合并的测试属性源值的访问。

声明测试属性源

你可以使用@TestPropertySource的locations或value属性配置测试属性文件。

支持传统和基于XML的属性文件格式 - 例如,“classpath:/com/example/test.properties”或“file:///path/to/file.xml”。

每个路径都被解释为Spring资源。 普通路径(例如,“test.properties”)被视为相对于定义测试类的包的类路径资源。 以斜杠开头的路径被视为绝对类路径资源(例如:“/ org / example / test.xml”)。 使用指定的资源协议加载引用URL的路径(例如,以classpath:,file:或http :)为前缀的路径。 不允许使用资源位置通配符(例如* /。属性):每个位置必须仅评估一个.properties或.xml资源。

以下示例使用测试属性文件:

@ContextConfiguration
@TestPropertySource("/test.properties") 
public class MyIntegrationTests {
    // class body...
}

你可以使用@TestPropertySource的properties属性以键值对的形式配置内联属性,如下一个示例所示。 所有键值对都作为具有最高优先级的单个测试PropertySource添加到封闭环境中。

键值对支持的语法与为Java属性文件中的条目定义的语法相同:

  • key=value

  • key:value

  • key value

以下示例设置两个内联属性:

@ContextConfiguration
@TestPropertySource(properties = {"timezone = GMT", "port: 4242"}) 
public class MyIntegrationTests {
    // class body...
}

默认属性文件检测

如果将@TestPropertySource声明为空注解(即,没有位置或属性属性的显式值),则会尝试检测相对于声明注解的类的默认属性文件。例如,如果带注解的测试类是com.example.MyTest,则相应的默认属性文件是classpath:com / example / MyTest.properties。如果无法检测到默认值,则抛出IllegalStateException。

优先权

测试属性源的优先级高于从操作系统环境,Java系统属性或应用程序通过使用@PropertySource或以编程方式声明性地添加的属性源的优先级。因此,测试属性源可用于有选择地覆盖系统和应用程序属性源中定义的属性。此外,内联属性的优先级高于从资源位置加载的属性。

在下一个示例中,时区和端口属性以及“/test.properties”中定义的任何属性都会覆盖在系统和应用程序属性源中定义的同名属性。此外,如果“/test.properties”文件定义了时区和端口属性的条目,那么这些条目将被使用properties属性声明的内联属性覆盖。以下示例显示如何在文件和内联中指定属性:

@ContextConfiguration
@TestPropertySource(
    locations = "/test.properties",
    properties = {"timezone = GMT", "port: 4242"}
)
public class MyIntegrationTests {
    // class body...
}

继承和覆盖测试属性源

@TestPropertySource支持boolean inheritLocations和inheritProperties属性,这些属性表示是否应继承属性文件的资源位置和超类声明的内联属性。两个标志的默认值均为true。这意味着测试类继承了任何超类声明的位置和内联属性。具体而言,测试类的位置和内联属性将附加到超类声明的位置和内联属性。因此,子类可以选择扩展位置和内联属性。请注意,稍后出现的属性会影响(即覆盖)先前出现的同名属性。此外,上述优先规则也适用于继承的测试属性源。

如果@TestPropertySource中的inheritLocations或inheritProperties属性设置为false,则测试类的位置或内联属性分别影响并有效地替换超类定义的配置。

在下一个示例中,仅使用base.properties文件作为测试属性源来加载BaseTest的ApplicationContext。相反,使用base.properties和extended.properties文件作为测试属性源位置来加载ExtendedTest的ApplicationContext。以下示例显示如何使用属性文件在子类及其超类中定义属性:

@TestPropertySource("base.properties")
@ContextConfiguration
public class BaseTest {
    // ...
}

@TestPropertySource("extended.properties")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
    // ...
}

在下一个示例中,仅使用内联key1属性加载BaseTest的ApplicationContext。 相反,使用内联的key1和key2属性加载ExtendedTest的ApplicationContext。 以下示例显示如何使用内联属性在子类及其超类中定义属性:

@TestPropertySource(properties = "key1 = value1")
@ContextConfiguration
public class BaseTest {
    // ...
}

@TestPropertySource(properties = "key2 = value2")
@ContextConfiguration
public class ExtendedTest extends BaseTest {
    // ...
}

Loading a WebApplicationContext

Spring 3.2引入了对在集成测试中加载WebApplicationContext的支持。要指示TestContext框架加载WebApplicationContext而不是标准ApplicationContext,可以使用@WebAppConfiguration注解相应的测试类。

测试类上存在@WebAppConfiguration指示TestContext框架(TCF)应为你的集成测试加载WebApplicationContext(WAC)。在后台,TCF确保创建MockServletContext并将其提供给测试的WAC。默认情况下,MockServletContext的基本资源路径设置为src / main / webapp。这被解释为相对于JVM根目录的路径(通常是项目的路径)。如果你熟悉Maven项目中Web应用程序的目录结构,则你知道src / main / webapp是WAR根目录的默认位置。如果需要覆盖此缺省值,则可以提供@WebAppConfiguration批注的备用路径(例如,@WebAppConfiguration(“src / test / webapp”))。如果你希望从类路径而不是文件系统引用基本资源路径,则可以使用Spring的classpath:前缀。

请注意,Spring对WebApplicationContext实现的测试支持与其对标准ApplicationContext实现的支持相同。使用WebApplicationContext进行测试时,可以使用@ContextConfiguration自由声明XML配置文件,Groovy脚本或@Configuration类。你也可以自由使用任何其他测试注解,例如@ActiveProfiles,@TestExecutionListeners,@Sql,@Rollback等。

本节中的其余示例显示了用于加载WebApplicationContext的一些配置选项。以下示例显示了TestContext框架对约定优于配置的支持:

@RunWith(SpringRunner.class)

// defaults to "file:src/main/webapp"
@WebAppConfiguration

// detects "WacTests-context.xml" in the same package
// or static nested @Configuration classes
@ContextConfiguration

public class WacTests {
    //...
}

如果使用@WebAppConfiguration注解测试类而未指定资源基路径,则资源路径有效默认为file:src / main / webapp。 类似地,如果声明@ContextConfiguration而未指定资源位置,带注解的类或上下文初始值设定项,Spring会尝试使用约定来检测配置是否存在(即WacTests-context.xml与WacTests类相同的包或静态) 嵌套的@Configuration类)。

以下示例说明如何使用@WebAppConfiguration显式声明资源基路径,并使用@ContextConfiguration显示XML资源位置:

@RunWith(SpringRunner.class)

// file system resource
@WebAppConfiguration("webapp")

// classpath resource
@ContextConfiguration("/spring/test-servlet-config.xml")

public class WacTests {
    //...
}

这里要注意的重要一点是具有这两个注解的路径的不同语义。 默认情况下,@WebAppConfiguration资源路径是基于文件系统的,而@ContextConfiguration资源位置是基于类路径的。

以下示例显示我们可以通过指定Spring资源前缀来覆盖两个注解的默认资源语义:

@RunWith(SpringRunner.class)

// classpath resource
@WebAppConfiguration("classpath:test-web-resources")

// file system resource
@ContextConfiguration("file:src/main/webapp/WEB-INF/servlet-config.xml")

public class WacTests {
    //...
}

使用Web Mocks

为了提供全面的Web测试支持,Spring 3.2引入了一个默认启用的ServletTestExecutionListener。在针对WebApplicationContext进行测试时,此TestExecutionListener在每个测试方法之前使用Spring Web的RequestContextHolder设置默认线程局部状态,并基于使用@WebAppConfiguration配置的基本资源路径创建MockHttpServletRequest,MockHttpServletResponse和ServletWebRequest。 ServletTestExecutionListener还确保可以将MockHttpServletResponse和ServletWebRequest注入到测试实例中,并且一旦测试完成,它就会清除线程本地状态。

一旦为测试加载了WebApplicationContext,你可能会发现需要与Web模拟进行交互 - 例如,设置测试夹具或在调用Web组件后执行断言。以下示例显示可以将哪些模拟自动装配到测试实例中。请注意,WebApplicationContext和MockServletContext都在测试套件中缓存,而其他模拟由ServletTestExecutionListener按照测试方法进行管理。

@WebAppConfiguration
@ContextConfiguration
public class WacTests {

    @Autowired
    WebApplicationContext wac; // cached

    @Autowired
    MockServletContext servletContext; // cached

    @Autowired
    MockHttpSession session;

    @Autowired
    MockHttpServletRequest request;

    @Autowired
    MockHttpServletResponse response;

    @Autowired
    ServletWebRequest webRequest;

    //...
}

Context Caching

一旦TestContext框架为测试加载ApplicationContext(或WebApplicationContext),该上下文就会被缓存并重用于在同一测试套件中声明相同唯一上下文配置的所有后续测试。要了解缓存的工作原理,了解“独特”和“测试套件”的含义非常重要。

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

  • locations(来自@ContextConfiguration)

  • classes(来自@ContextConfiguration)

  • contextInitializerClasses(来自@ContextConfiguration)

  • contextCustomizers(来自ContextCustomizerFactory)

  • contextLoader(来自@ContextConfiguration)

  • parent(来自@ContextHierarchy)

  • activeProfiles(来自@ActiveProfiles)

  • propertySourceLocations(来自@TestPropertySource)

  • propertySourceProperties(来自@TestPropertySource)

  • resourceBasePath(来自@WebAppConfiguration)

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

测试套件和分叉流程

Spring TestContext框架将应用程序上下文存储在静态缓存中。 这意味着上下文实际上存储在静态变量中。 换句话说,如果测试在单独的进程中执行,则在每次测试执行之间清除静态高速缓存,这有效地禁用了高速缓存机制。

要从缓存机制中受益,所有测试必须在同一进程或测试套件中运行。 这可以通过在IDE中作为一个组执行所有测试来实现。 类似地,当使用诸如Ant,Maven或Gradle之类的构建框架执行测试时,确保构建框架不在测试之间进行分配是很重要的。 例如,如果Maven Surefire插件的forkMode设置为always或pertest,则TestContext框架无法在测试类之间缓存应用程序上下文,因此构建过程运行速度明显更慢。

从Spring Framework 4.3开始,上下文缓存的大小受限于默认的最大大小32.每当达到最大大小时,最近最少使用(LRU)驱逐策略用于驱逐和关闭过时的上下文。你可以通过设置名为spring.test.context.cache.maxSize的JVM系统属性,从命令行或构建脚本配置最大大小。或者,你可以使用SpringProperties API以编程方式设置相同的属性。

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

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

Context Hierarchies

在编写依赖于加载的Spring ApplicationContext的集成测试时,通常可以针对单个上下文进行测试。但是,有时候对ApplicationContext实例的层次结构进行测试是有益的,甚至是必要的。例如,如果你正在开发Spring MVC Web应用程序,则通常会有Spring的ContextLoaderListener加载的根WebApplicationContext和Spring的DispatcherServlet加载的子WebApplicationContext。这将生成父子上下文层次结构,其中共享组件和基础结构配置在根上下文中声明,并由特定于Web的组件在子上下文中使用。可以在Spring Batch应用程序中找到另一个用例,其中你经常有一个父上下文,它提供共享批处理基础结构的配置,以及一个用于配置特定批处理作业的子上下文。

从Spring Framework 3.2.2开始,你可以编写使用上下文层次结构的集成测试,方法是在单个测试类或测试类层次结构中使用@ContextHierarchy注解声明上下文配置。如果在测试类层次结构中的多个类上声明了上下文层次结构,则还可以合并或覆盖上下文层次结构中特定的命名级别的上下文配置。合并层次结构中给定级别的配置时,配置资源类型(即XML配置文件或带注解的类)必须一致。否则,在使用不同资源类型配置的上下文层次结构中具有不同级别是完全可以接受的。

本节中剩余的基于JUnit 4的示例显示了需要使用上下文层次结构的集成测试的常见配置方案。

带上下文层次结构的单个测试类

ControllerIntegrationTests表示Spring MVC Web应用程序的典型集成测试场景,它声明了一个由两个级别组成的上下文层次结构,一个用于根WebApplicationContext(使用TestAppConfig @Configuration类加载),另一个用于调度程序servlet WebApplicationContext(通过使用加载) WebConfig @Configuration类)。自动装配到测试实例中的WebApplicationContext是子上下文的WebApplicationContext(即层次结构中的最低上下文)。以下清单显示了此配置方案:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextHierarchy({
    @ContextConfiguration(classes = TestAppConfig.class),
    @ContextConfiguration(classes = WebConfig.class)
})
public class ControllerIntegrationTests {

    @Autowired
    private WebApplicationContext wac;

    // ...
}

具有隐式父上下文的类层次结构

此示例中的测试类定义测试类层次结构中的上下文层次结构。 AbstractWebTests在Spring驱动的Web应用程序中声明根WebApplicationContext的配置。 但请注意,AbstractWebTests不声明@ContextHierarchy。 因此,AbstractWebTests的子类可以选择参与上下文层次结构或遵循@ContextConfiguration的标准语义。 SoapWebServiceTests和RestWebServiceTests都扩展了AbstractWebTests并使用@ContextHierarchy定义了一个上下文层次结构。 结果是加载了三个应用程序上下文(每个@ContextConfiguration声明一个),并且基于AbstractWebTests中的配置加载的应用程序上下文被设置为为具体子类加载的每个上下文的父上下文。 以下清单显示了此配置方案:

@RunWith(SpringRunner.class)
@WebAppConfiguration
@ContextConfiguration("file:src/main/webapp/WEB-INF/applicationContext.xml")
public abstract class AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/soap-ws-config.xml")
public class SoapWebServiceTests extends AbstractWebTests {}

@ContextHierarchy(@ContextConfiguration("/spring/rest-ws-config.xml")
public class RestWebServiceTests extends AbstractWebTests {}

具有合并的上下文层次结构配置的类层次

此示例中的类显示了命名层次结构级别的使用,以便合并上下文层次结构中特定级别的配置。 BaseTests在层次结构中定义了两个级别,父级和子级。 ExtendedTests扩展了BaseTests,并通过确保@ContextConfiguration中name属性中声明的名称都是子级,指示Spring TestContext Framework合并子层次结构级别的上下文配置。 结果是加载了三个应用程序上下文:一个用于/app-config.xml,一个用于/user-config.xml,另一个用于{“/ user-config.xml”,“/ order-config.xml”}。 与前面的示例一样,从/app-config.xml加载的应用程序上下文被设置为从/user-config.xml和{“/ user-config.xml”,“/ order-config”加载的上下文的父上下文.XML“}。 以下清单显示了此配置方案:

@RunWith(SpringRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(name = "child", locations = "/order-config.xml")
)
public class ExtendedTests extends BaseTests {}

具有重写的上下文层次结构配置的类层次结构

与前面的示例相比,此示例演示了如何通过将@ContextConfiguration中的inheritLocations标志设置为false来覆盖上下文层次结构中给定命名级别的配置。 因此,ExtendedTests的应用程序上下文仅从/test-user-config.xml加载,并将其父级设置为从/app-config.xml加载的上下文。 以下清单显示了此配置方案:

@RunWith(SpringRunner.class)
@ContextHierarchy({
    @ContextConfiguration(name = "parent", locations = "/app-config.xml"),
    @ContextConfiguration(name = "child", locations = "/user-config.xml")
})
public class BaseTests {}

@ContextHierarchy(
    @ContextConfiguration(
        name = "child",
        locations = "/test-user-config.xml",
        inheritLocations = false
))
public class ExtendedTests extends BaseTests {}

在上下文层次结构中消除上下文

如果在其上下文配置为上下文层次结构的一部分的测试中使用@DirtiesContext,则可以使用hierarchyMode标志来控制如何清除上下文高速缓存。 有关更多详细信息,请参阅Spring Testing Annotations中的@DirtiesContext和@DirtiesContext javadoc的讨论。

3.5.5 测试夹具的依赖注入

使用DependencyInjectionTestExecutionListener(默认配置)时,测试实例的依赖项将从使用@ContextConfiguration或相关注解配置的应用程序上下文中的bean中注入。 你可以使用setter injection,field injection或两者,具体取决于你选择的注解以及是否将它们放在setter方法或字段上。 如果你正在使用JUnit Jupiter,你也可以选择使用构造函数注入(请参阅使用SpringExtension的依赖注入)。 为了与Spring基于注解的注入支持保持一致,你还可以使用Spring的@Autowired注解或JSR-330的@Inject注解进行字段和setter注入。

为了测试JUnit Jupiter以外的框架,TestContext框架不参与测试类的实例化。 因此,对构造函数使用@Autowired或@Inject对测试类没有影响。

虽然在生产代码中不鼓励字段注入,但实际注入在测试代码中实际上非常自然。 差异的基本原理是你永远不会直接实例化你的测试类。 因此,无需在测试类上调用公共构造函数或setter方法。

因为@Autowired用于按类型执行自动装配,如果你有多个相同类型的bean定义,则不能依赖此方法来处理这些特定的bean。在这种情况下,你可以将@Autowired与@Qualifier结合使用。从Spring 3.0开始,你还可以选择将@Inject与@Named结合使用。或者,如果你的测试类可以访问其ApplicationContext,则可以使用(例如)对applicationContext.getBean(“titleRepository”,TitleRepository.class)的调用来执行显式查找。

如果你不希望将依赖项注入应用于测试实例,请不要使用@Autowired或@Inject注解字段或setter方法。或者,你可以通过使用@TestExecutionListeners显式配置类并从侦听器列表中省略DependencyInjectionTestExecutionListener.class来完全禁用依赖项注入。

考虑一下测试HibernateTitleRepository类的场景,如目标部分所述。接下来的两个代码清单演示了在字段和setter方法上使用@Autowired。在所有示例代码列表之后呈现应用程序上下文配置。

以下代码清单中的依赖项注入行为并非特定于JUnit 4.相同的DI技术可与任何支持的测试框架结合使用。

以下示例调用静态断言方法,例如assertNotNull(),但不使用Assert预先调用。 在这种情况下,假设通过示例中未显示的import static 声明正确导入了该方法。

第一个代码清单显示了一个基于JUnit 4的测试类实现,它使用@Autowired进行字段注入:

@RunWith(SpringRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    @Autowired
    private HibernateTitleRepository titleRepository;

    @Test
    public void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

或者,你可以将类配置为使用@Autowired进行setter注入,如下所示:

@RunWith(SpringRunner.class)
// specifies the Spring configuration to load for this test fixture
@ContextConfiguration("repository-config.xml")
public class HibernateTitleRepositoryTests {

    // this instance will be dependency injected by type
    private HibernateTitleRepository titleRepository;

    @Autowired
    public void setTitleRepository(HibernateTitleRepository titleRepository) {
        this.titleRepository = titleRepository;
    }

    @Test
    public void findById() {
        Title title = titleRepository.findById(new Long(10));
        assertNotNull(title);
    }
}

前面的代码清单使用@ContextConfiguration批注引用的相同XML上下文文件(即repository-config.xml)。 以下显示了此配置:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- this bean will be injected into the HibernateTitleRepositoryTests class -->
    <bean id="titleRepository" class="com.foo.repository.hibernate.HibernateTitleRepository">
        <property name="sessionFactory" ref="sessionFactory"/>
    </bean>

    <bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
        <!-- configuration elided for brevity -->
    </bean>

</beans>

如果你从Spring提供的测试基类扩展到其自身的一个setter方法上使用@Autowired,则可能在应用程序上下文中定义了多个受影响类型的bean(例如,多个DataSource bean)。 在这种情况下,你可以覆盖setter方法并使用@Qualifier批注指示特定的目标bean,如下所示(但请确保也委托给超类中的重写方法):

// ...

    @Autowired
    @Override
    public void setDataSource(@Qualifier("myDataSource") DataSource dataSource) {
        super.setDataSource(dataSource);
    }

// ...

指定的限定符值指示要注入的特定DataSource bean,将类型匹配集缩小到特定bean。 它的值与相应<bean>定义中的<qualifier>声明匹配。 bean名称用作回退限定符值,因此你还可以有效地指向特定的bean(如前所示,假设myDataSource是bean id)。

3.5.6 测试Request- 和Session-scoped Beans

Spring从早年开始就支持Request和session-scoped bean。从Spring 3.2开始,你可以按照以下步骤测试请求范围和会话范围的bean:

  • 通过使用@WebAppConfiguration注解测试类,确保为测试加载WebApplicationContext。

  • 将模拟请求或会话注入测试实例并根据需要准备测试夹具。

  • 调用从配置的WebApplicationContext检索的Web组件(具有依赖项注入)。

  • 对模拟执行断言。

下一个代码段显示了登录用例的XML配置。请注意,userService bean依赖于请求范围的loginAction bean。此外,LoginAction是通过使用SpEL表达式实例化的,该表达式从当前HTTP请求中检索用户名和密码。在我们的测试中,我们希望通过TestContext框架管理的模拟配置这些请求参数。以下清单显示了此用例的配置:

<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:loginAction-ref="loginAction"/>

    <bean id="loginAction" class="com.example.LoginAction"
            c:username="#{request.getParameter('user')}"
            c:password="#{request.getParameter('pswd')}"
            scope="request">
        <aop:scoped-proxy/>
    </bean>

</beans>

在RequestScopedBeanTests中,我们将UserService(即测试中的主题)和MockHttpServletRequest注入我们的测试实例。 在我们的requestScope()测试方法中,我们通过在提供的MockHttpServletRequest中设置请求参数来设置我们的测试夹具。 当在我们的userService上调用loginUser()方法时,我们可以确保用户服务可以访问当前MockHttpServletRequest的请求范围的loginAction(也就是我们刚刚设置参数的那个)。 然后,我们可以根据用户名和密码的已知输入对结果执行断言。 以下清单显示了如何执行此操作:

@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class RequestScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpServletRequest request;

    @Test
    public void requestScope() {
        request.setParameter("user", "enigma");
        request.setParameter("pswd", "$pr!ng");

        LoginResults results = userService.loginUser();
        // assert results
    }
}

以下代码片段类似于我们之前针对请求范围的bean看到的代码片段。 但是,这次,userService bean依赖于会话范围的userPreferences bean。 请注意,UserPreferences bean是使用SpEL表达式实例化的,该表达式从当前HTTP会话中检索主题。 在我们的测试中,我们需要在TestContext框架管理的模拟会话中配置主题。 以下示例显示了如何执行此操作:

<beans>

    <bean id="userService" class="com.example.SimpleUserService"
            c:userPreferences-ref="userPreferences" />

    <bean id="userPreferences" class="com.example.UserPreferences"
            c:theme="#{session.getAttribute('theme')}"
            scope="session">
        <aop:scoped-proxy/>
    </bean>

</beans>

在SessionScopedBeanTests中,我们将UserService和MockHttpSession注入到我们的测试实例中。 在我们的sessionScope()测试方法中,我们通过在提供的MockHttpSession中设置预期的主题属性来设置我们的测试夹具。 当我们的userService上调用processUserPreferences()方法时,我们可以确保用户服务可以访问当前MockHttpSession的会话范围的userPreferences,并且我们可以根据配置的主题对结果执行断言。 以下示例显示了如何执行此操作:

@RunWith(SpringRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class SessionScopedBeanTests {

    @Autowired UserService userService;
    @Autowired MockHttpSession session;

    @Test
    public void sessionScope() throws Exception {
        session.setAttribute("theme", "blue");

        Results results = userService.processUserPreferences();
        // assert results
    }
}

3.5.7 事物管理

在TestContext框架中,事务由TransactionalTestExecutionListener管理,即使你未在测试类上显式声明@TestExecutionListeners,也会默认配置该事务。但是,要启用对事务的支持,必须在ApplicationContext中配置一个加载了@ContextConfiguration语义的PlatformTransactionManager bean(稍后会提供更多详细信息)。此外,你必须在类或方法级别为测试声明Spring的@Transactional注解。

测试管理的事务

测试管理的事务是通过使用TransactionalTestExecutionListener以编程方式管理的事务,或使用TestTransaction以编程方式管理的事务(稍后介绍)。你不应将此类事务与Spring管理的事务(在为测试加载的ApplicationContext中由Spring直接管理的事务)或应用程序管理的事务(在测试调用的应用程序代码中以编程方式管理的事务)混淆。 Spring管理和应用程序管理的事务通常参与测试管理的事务。但是,如果Spring管理的事务或应用程序管理的事务配置了除REQUIRED或SUPPORTS之外的任何传播类型,则应谨慎使用(有关详细信息,请参阅有关事务传播的讨论)。

抢先超时和测试管理的交易

当使用来自测试框架的任何形式的抢先超时以及Spring的测试管理事务时,必须小心。

具体来说,在调用当前测试方法之前,Spring的测试支持将事务状态绑定到当前线程(通过java.lang.ThreadLocal变量)。如果测试框架在新线程中调用当前测试方法以支持抢占超时,则不会在测试管理事务中调用当前测试方法中执行的任何操作。因此,任何此类操作的结果都不会与测试管理的事务一起回滚。相反,即使测试管理的事务由Spring正确回滚,这些操作也将被提交到持久存储 - 例如,关系数据库。

可能发生这种情况包括但不限于以下情况。

JUnit 4的@Test(timeout = ...)支持和TimeOut规则

JUnit Jupiter在org.junit.jupiter.api.Assertions类中的assertTimeoutPreemptively(...)方法

TestNG的@Test(timeOut = ...)支持

启用和禁用事务

使用@Transactional注解测试方法会导致测试在事务中运行,默认情况下,该事务在测试完成后自动回滚。 如果使用@Transactional注解测试类,则该类层次结构中的每个测试方法都在事务中运行。 未使用@Transactional注解的测试方法(在类或方法级别)不在事务中运行。 此外,使用@Transactional注解但传播类型设置为NOT_SUPPORTED的测试不会在事务中运行。

请注意,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests已预先配置为类级别的事务支持。

以下示例演示了为基于Hibernate的UserRepository编写集成测试的常见方案:

@RunWith(SpringRunner.class)
@ContextConfiguration(classes = TestConfig.class)
@Transactional
public class HibernateUserRepositoryTests {

    @Autowired
    HibernateUserRepository repository;

    @Autowired
    SessionFactory sessionFactory;

    JdbcTemplate jdbcTemplate;

    @Autowired
    public void setDataSource(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    public void createUser() {
        // track initial state in test database:
        final int count = countRowsInTable("user");

        User user = new User(...);
        repository.save(user);

        // Manual flush is required to avoid false positive in test
        sessionFactory.getCurrentSession().flush();
        assertNumUsers(count + 1);
    }

    protected int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

如事务回滚和提交行为中所述,在运行createUser()方法后无需清理数据库,因为对TransactionalTestExecutionListener会自动回滚对数据库所做的任何更改。

事务回滚和提交行为

默认情况下,测试完成后将自动回滚测试事务; 但是,事务提交和回滚行为可以通过@Commit和@Rollback注解以声明方式配置。 有关更多详细信息,请参阅注解支持部分中的相应条目。

程序化交易管理

从Spring Framework 4.1开始,你可以使用TestTransaction中的静态方法以编程方式与测试管理的事务进行交互。 例如,你可以在测试方法中,在方法之前,在方法之后使用TestTransaction来启动或结束当前的测试管理事务,或者为回滚或提交配置当前测试管理的事务。 每当启用TransactionalTestExecutionListener时,都会自动提供对TestTransaction的支持。

以下示例演示了TestTransaction的一些功能。 有关详细信息,请参阅TestTransaction的javadoc。

@ContextConfiguration(classes = TestConfig.class)
public class ProgrammaticTransactionManagementTests extends
        AbstractTransactionalJUnit4SpringContextTests {

    @Test
    public void transactionalTest() {
        // assert initial state in test database:
        assertNumUsers(2);

        deleteFromTables("user");

        // changes to the database will be committed!
        TestTransaction.flagForCommit();
        TestTransaction.end();
        assertFalse(TestTransaction.isActive());
        assertNumUsers(0);

        TestTransaction.start();
        // perform other actions against the database that will
        // be automatically rolled back after the test completes...
    }

    protected void assertNumUsers(int expected) {
        assertEquals("Number of rows in the [user] table.", expected, countRowsInTable("user"));
    }
}

在交易之外运行代码

有时,你可能需要在事务测试方法之前或之后但在事务上下文之外执行某些代码 - 例如,在运行测试之前验证初始数据库状态或在测试运行后验证预期的事务提交行为(如果 test配置为提交事务)。 TransactionalTestExecutionListener完全支持@BeforeTransaction和@AfterTransaction注解。 你可以使用其中一个注解在测试类或测试接口中的任何void default方法中注解任何void方法,并且TransactionalTestExecutionListener可确保你的before transaction方法或事务方法在适当的时间运行。

之前的任何方法(例如使用JUnit Jupiter的@BeforeEach注解的方法)和任何后续方法(例如使用JUnit Jupiter的@AfterEach注解的方法)都在事务中运行。 此外,对于未配置为在事务中运行的测试方法,不会运行使用@BeforeTransaction或@AfterTransaction注解的方法。

配置事务管理器

TransactionalTestExecutionListener期望在Spring ApplicationContext中为测试定义PlatformTransactionManager bean。如果测试的ApplicationContext中有多个PlatformTransactionManager实例,则可以使用@Transactional(“myTxMgr”)或@Transactional(transactionManager =“myTxMgr”)声明限定符,或者可以通过@Configuration类实现TransactionManagementConfigurer。有关用于在测试的ApplicationContext中查找事务管理器的算法的详细信息,请参阅testContextTransactionUtils.retrieveTransactionManager()的javadoc。

演示所有与交易相关的注解

以下基于JUnit 4的示例显示了一个虚构的集成测试场景,该场景突出显示了所有与事务相关的注解。该示例不是为了演示最佳实践,而是为了演示如何使用这些注解。有关更多信息和配置示例,请参阅注解支持部分。 @Sql的事务管理包含一个使用@Sql进行声明性SQL脚本执行并使用默认事务回滚语义的附加示例。以下示例显示了相关注解:

@RunWith(SpringRunner.class)
@ContextConfiguration
@Transactional(transactionManager = "txMgr")
@Commit
public class FictitiousTransactionalTest {

    @BeforeTransaction
    void verifyInitialDatabaseState() {
        // logic to verify the initial state before a transaction is started
    }

    @Before
    public void setUpTestDataWithinTransaction() {
        // set up test data within the transaction
    }

    @Test
    // overrides the class-level @Commit setting
    @Rollback
    public void modifyDatabaseWithinTransaction() {
        // logic which uses the test data and modifies database state
    }

    @After
    public void tearDownWithinTransaction() {
        // execute "tear down" logic within the transaction
    }

    @AfterTransaction
    void verifyFinalDatabaseState() {
        // logic to verify the final state after transaction has rolled back
    }

}

在测试ORM代码时避免误报

当你测试操作Hibernate会话或JPA持久性上下文的状态的应用程序代码时,请确保在运行该代码的测试方法中刷新基础工作单元。 未能刷新基础工作单元可能会产生误报:你的测试通过,但相同的代码会在实时生产环境中引发异常。 请注意,这适用于维护内存工作单元的任何ORM框架。 在下面基于Hibernate的示例测试用例中,一个方法演示了误报,另一个方法正确公开了刷新会话的结果:

// ...

@Autowired
SessionFactory sessionFactory;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInHibernateSession();
    // False positive: an exception will be thrown once the Hibernate
    // Session is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithSessionFlush() {
    updateEntityInHibernateSession();
    // Manual flush is required to avoid false positive in test
    sessionFactory.getCurrentSession().flush();
}

以下示例显示JPA的匹配方法:

// ...

@PersistenceContext
EntityManager entityManager;

@Transactional
@Test // no expected exception!
public void falsePositive() {
    updateEntityInJpaPersistenceContext();
    // False positive: an exception will be thrown once the JPA
    // EntityManager is finally flushed (i.e., in production code)
}

@Transactional
@Test(expected = ...)
public void updateWithEntityManagerFlush() {
    updateEntityInJpaPersistenceContext();
    // Manual flush is required to avoid false positive in test
    entityManager.flush();
}

3.5.8 执行SQL脚本

在针对关系数据库编写集成测试时,执行SQL脚本来修改数据库模式或将测试数据插入表中通常是有益的。 spring-jdbc模块通过在加载Spring ApplicationContext时执行SQL脚本来支持初始化嵌入或现有数据库。有关详细信息,请参阅嵌入式数据库支持和使用嵌入式数据库测试数据

尽管在加载ApplicationContext时初始化数据库以进行一次测试非常有用,但有时在集成测试期间能够修改数据库是至关重要的。以下部分说明如何在集成测试期间以编程方式和声明方式执行SQL脚本。

以编程方式执行SQL脚本

Spring提供了以下选项,用于在集成测试方法中以编程方式执行SQL脚本。

org.springframework.jdbc.datasource.init.ScriptUtils

org.springframework.jdbc.datasource.init.ResourceDatabasePopulator

org.springframework.test.context.junit4.AbstractTransactionalJUnit4SpringContextTests

org.springframework.test.context.testng.AbstractTransactionalTestNGSpringContextTests

ScriptUtils提供了一组用于处理SQL脚本的静态实用程序方法,主要用于框架内部使用。但是,如果你需要完全控制SQL脚本的解析和执行方式,那么ScriptUtils可能比后面描述的其他一些替代方案更适合你的需求。有关更多详细信息,请参阅ScriptUtils中各个方法的javadoc。

ResourceDatabasePopulator提供基于对象的API,以便使用外部资源中定义的SQL脚本以编程方式填充,初始化或清理数据库。 ResourceDatabasePopulator提供用于配置解析和运行脚本时使用的字符编码,语句分隔符,注解分隔符和错误处理标志的选项。每个配置选项都有一个合理的默认值。有关默认值的详细信息,请参阅javadoc。要运行在ResourceDatabasePopulator中配置的脚本,可以调用populate(Connection)方法对java.sql.Connection执行populator,或者调用execute(DataSource)方法对javax.sql.DataSource执行populator。以下示例为测试模式和测试数据指定SQL脚本,将语句分隔符设置为@@,并针对DataSource执行脚本:

@Test
public void databaseTest {
    ResourceDatabasePopulator populator = new ResourceDatabasePopulator();
    populator.addScripts(
            new ClassPathResource("test-schema.sql"),
            new ClassPathResource("test-data.sql"));
    populator.setSeparator("@@");
    populator.execute(this.dataSource);
    // execute code that uses the test schema and data
}

请注意,ResourceDatabasePopulator内部委托给ScriptUtils来解析和运行SQL脚本。类似地,AbstractTransactionalJUnit4SpringContextTests和AbstractTransactionalTestNGSpringContextTests中的executeSqlScript(..)方法在内部使用ResourceDatabasePopulator来运行SQL脚本。有关更多详细信息,请参阅javadoc以获取各种executeSqlScript(..)方法。

使用@Sql以声明方式执行SQL脚本

除了上述以编程方式运行SQL脚本的机制之外,你还可以在Spring TestContext Framework中声明性地配置SQL脚本。具体来说,你可以在测试类或测试方法上声明@Sql批注,以配置应在集成测试方法之前或之后针对给定数据库运行的SQL脚本的资源路径。请注意,方法级声明会覆盖类级声明,并且SqlScriptsTestExecutionListener会提供对@Sql的支持,默认情况下会启用它。

路径资源语义

每个路径都被解释为Spring资源。普通路径(例如,“schema.sql”)被视为相对于定义测试类的包的类路径资源。以斜杠开头的路径被视为绝对类路径资源(例如,“/ org / example / sysma.sql”)。引用URL的路径(例如,以classpath:,file:,http :)为前缀的路径是使用指定的资源协议加载的。

以下示例显示如何在类级别和基于JUnit Jupiter的集成测试类中的方法级别使用@Sql:

@SpringJUnitConfig
@Sql("/test-schema.sql")
class DatabaseTests {

    @Test
    void emptySchemaTest {
        // execute code that uses the test schema without any test data
    }

    @Test
    @Sql({"/test-schema.sql", "/test-user-data.sql"})
    void userTest {
        // execute code that uses the test schema and test data
    }
}

默认脚本检测

如果未指定SQL脚本,则会尝试检测默认脚本,具体取决于声明@Sql的位置。如果无法检测到默认值,则抛出IllegalStateException。

  • 类级声明:如果带注解的测试类是com.example.MyTest,则相应的默认脚本是classpath:com / example / MyTest.sql。

  • 方法级声明:如果带注解的测试方法名为testMethod()并且在类com.example.MyTest中定义,则相应的默认脚本为classpath:com / example / MyTest.testMethod.sql。

声明多个@Sql集

如果需要为给定的测试类或测试方法配置多组SQL脚本,但具有不同的语法配置,不同的错误处理规则或每组不同的执行阶段,则可以声明多个@Sql实例。使用Java 8,你可以使用@Sql作为可重复的注解。否则,你可以使用@SqlGroup批注作为显式容器来声明@Sql的多个实例。

以下示例显示如何将@Sql用作Java 8的可重复注解:

@Test
@Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`"))
@Sql("/test-user-data.sql")
public void userTest {
    // execute code that uses the test schema and test data
}

在前面示例中显示的方案中,test-schema.sql脚本对单行注解使用不同的语法。

以下示例与前面的示例相同,只是@Sql声明在@SqlGroup中组合在一起,以便与Java 6和Java 7兼容。

@Test
@SqlGroup({
    @Sql(scripts = "/test-schema.sql", config = @SqlConfig(commentPrefix = "`")),
    @Sql("/test-user-data.sql")
)}
public void userTest {
    // execute code that uses the test schema and test data
}

脚本执行阶段

默认情况下,SQL脚本在相应的测试方法之前执行。 但是,如果需要在测试方法之后运行特定的脚本集(例如,清理数据库状态),则可以使用@Sql中的executionPhase属性,如以下示例所示:

@Test
@Sql(
    scripts = "create-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED)
)
@Sql(
    scripts = "delete-test-data.sql",
    config = @SqlConfig(transactionMode = ISOLATED),
    executionPhase = AFTER_TEST_METHOD
)
public void userTest {
    // execute code that needs the test data to be committed
    // to the database outside of the test's transaction
}

请注意,ISOLATED和AFTER_TEST_METHOD分别从Sql.TransactionMode和Sql.ExecutionPhase静态导入。

使用@SqlConfig进行脚本配置

你可以使用@SqlConfig批注配置脚本解析和错误处理。当在集成测试类上声明为类级别注解时,@SqlConfig将作为测试类层次结构中所有SQL脚本的全局配置。当使用@Sql批注的config属性直接声明时,@SqlConfig用作在封闭的@Sql批注中声明的SQL脚本的本地配置。 @SqlConfig中的每个属性都有一个隐式默认值,该值在相应属性的javadoc中记录。由于在Java语言规范中为注解属性定义了规则,遗憾的是,不能将值null赋给注解属性。因此,为了支持继承的全局配置的覆盖,@SqlConfig属性具有显式默认值“”(对于字符串)或DEFAULT(对于枚举)。这种方法允许@SqlConfig的本地声明通过提供除“”或DEFAULT之外的值来有选择地覆盖@SqlConfig的全局声明中的各个属性。只要本地@SqlConfig属性不提供“”或“DEFAULT”以外的显式值,就会继承全局@SqlConfig属性。因此,显式本地配置会覆盖全局配置。

@Sql和@SqlConfig提供的配置选项等同于ScriptUtils和ResourceDatabasePopulator支持的配置选项,但它们是<jdbc:initialize-database /> XML命名空间元素提供的超集。有关详细信息,请参阅@Sql和@SqlConfig中各个属性的javadoc。

@Sql的事务管理

默认情况下,SqlScriptsTestExecutionListener为使用@Sql配置的脚本推断所需的事务语义。具体而言,SQL脚本在没有事务的情况下运行,在现有的Spring管理的事务中(例如,由TransactionalTestExecutionListener管理的事务,用于使用@Transactional注解的测试),或者在隔离的事务中运行,具体取决于transactionMode的配置值@SqlConfig中的属性以及测试的ApplicationContext中是否存在PlatformTransactionManager。但是,作为最低限度,测试的ApplicationContext中必须存在javax.sql.DataSource。

如果SqlScriptsTestExecutionListener用于检测DataSource和PlatformTransactionManager并推断事务语义的算法不符合你的需要,则可以通过设置@SqlConfig的dataSource和transactionManager属性来指定显式名称。此外,你可以通过设置@SqlConfig的transactionMode属性来控制事务传播行为(例如,脚本是否应该在隔离的事务中运行)。虽然对使用@Sql进行事务管理的所有受支持选项的详尽讨论超出了本参考手册的范围,但@SqlConfig和SqlScriptsTestExecutionListener的javadoc提供了详细信息,以下示例显示了使用JUnit Jupiter和事务测试的典型测试方案与@Sql:

@SpringJUnitConfig(TestDatabaseConfig.class)
@Transactional
class TransactionalSqlScriptsTests {

    final JdbcTemplate jdbcTemplate;

    @Autowired
    TransactionalSqlScriptsTests(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    @Test
    @Sql("/test-data.sql")
    void usersTest() {
        // verify state in test database:
        assertNumUsers(2);
        // execute code that uses the test data...
    }

    int countRowsInTable(String tableName) {
        return JdbcTestUtils.countRowsInTable(this.jdbcTemplate, tableName);
    }

    void assertNumUsers(int expected) {
        assertEquals(expected, countRowsInTable("user"),
            "Number of rows in the [user] table.");
    }
}

请注意,运行usersTest()方法后无需清理数据库,因为对数据库所做的任何更改(在测试方法内或在/test-data.sql脚本中)都会自动回滚。 TransactionalTestExecutionListener(有关详细信息,请参阅事务管理)。

3.5.9 并行测试执行

Spring Framework 5.0引入了在使用Spring TestContext Framework时在单个JVM中并行执行测试的基本支持。 通常,这意味着大多数测试类或测试方法可以并行执行,而无需对测试代码或配置进行任何更改。

有关如何设置并行测试执行的详细信息,请参阅测试框架,构建工具或IDE的文档。

请记住,在测试套件中引入并发可能会导致意外的副作用,奇怪的运行时行为以及间歇性或看似随机失败的测试。 因此,Spring Team提供了以下关于何时不并行执行测试的一般指导原则。

如果测试,请不要并行执行测试:

使用Spring的@DirtiesContext支持。

使用JUnit 4的@FixMethodOrder支持或任何旨在确保测试方法按特定顺序运行的测试框架功能。 但请注意,如果并行执行整个测试类,则不适用。

更改共享服务或系统的状态,例如数据库,消息代理,文件系统等。 这适用于内存和外部系统。

如果并行测试执行失败,并且异常表明当前测试的ApplicationContext不再处于活动状态,这通常意味着ApplicationContext已从另一个线程中的ContextCache中删除。

这可能是由于使用了@DirtiesContext或者是由于ContextCache的自动驱逐。 如果@DirtiesContext是罪魁祸首,你需要找到一种方法来避免使用@DirtiesContext或从并行执行中排除此类测试。 如果已超出ContextCache的最大大小,则可以增加高速缓存的最大大小。 有关详细信息,请参阅有关上下文缓存的讨论

Spring TestContext Framework中的并行测试执行只有在底层TestContext实现提供复制构造函数时才有可能,如testContext的javadoc中所述。 Spring中使用的DefaultTestContext提供了这样的构造函数。 但是,如果你使用提供自定义TestContext实现的第三方库,则需要验证它是否适合并行测试执行。

3.5.10 TestContext框架支持类

本节描述了支持Spring TestContext Framework的各种类。

Spring JUnit 4 Runner

Spring TestContext Framework通过自定义运行器(在JUnit 4.12或更高版本上支持)提供与JUnit 4的完全集成。通过使用@RunWith(SpringJUnit4ClassRunner.class)或更短的@RunWith(SpringRunner.class)变体来注解测试类,开发人员可以实现标准的基于JUnit 4的单元和集成测试,同时获得TestContext框架的好处,例如支持加载应用程序上下文,测试实例的依赖项注入,事务测试方法执行等。如果你想将Spring TestContext Framework与替代运行器(例如JUnit 4的Parameterized runner)或第三方运行程序(例如MockitoJUnitRunner)一起使用,你可以选择使用Spring对JUnit规则的支持。

以下代码清单显示了配置测试类以使用自定义Spring Runner运行的最低要求:

@RunWith(SpringRunner.class)
@TestExecutionListeners({})
public class SimpleTest {

    @Test
    public void testMethod() {
        // execute test logic...
    }
}

在前面的示例中,@TestExecutionListeners配置了一个空列表,以禁用默认侦听器,否则需要通过@ContextConfiguration配置ApplicationContext。

Spring JUnit 4规则

org.springframework.test.context.junit4.rules包提供以下JUnit 4规则(JUnit 4.12或更高版本支持):

  • SpringClassRule

  • SpringMethodRule

SpringClassRule是一个JUnit TestRule,它支持Spring TestContext Framework的类级功能,而SpringMethodRule是一个JUnit MethodRule,它支持Spring TestContext Framework的实例级和方法级功能。

与SpringRunner相比,Spring基于规则的JUnit支持具有独立于任何org.junit.runner.Runner实现的优势,因此可以与现有的替代运行程序(例如JUnit 4的Parameterized)或第三方结合使用跑步者(如MockitoJUnitRunner)。

要支持TestContext框架的完整功能,必须将SpringClassRule与SpringMethodRule结合使用。以下示例显示了在集成测试中声明这些规则的正确方法:

// Optionally specify a non-Spring Runner via @RunWith(...)
@ContextConfiguration
public class IntegrationTest {

    @ClassRule
    public static final SpringClassRule springClassRule = new SpringClassRule();

    @Rule
    public final SpringMethodRule springMethodRule = new SpringMethodRule();

    @Test
    public void testMethod() {
        // execute test logic...
    }
}

JUnit 4支持类

org.springframework.test.context.junit4包为基于JUnit 4的测试用例提供以下支持类(在JUnit 4.12或更高版本上受支持):

  • AbstractJUnit4SpringContextTests

  • AbstractTransactionalJUnit4SpringContextTests

AbstractJUnit4SpringContextTests是一个抽象基础测试类,它将Spring TestContext Framework与JUnit 4环境中的显式ApplicationContext测试支持集成在一起。扩展AbstractJUnit4SpringContextTests时,可以访问受保护的applicationContext实例变量,该变量可用于执行显式bean查找或测试整个上下文的状态。

AbstractTransactionalJUnit4SpringContextTests是AbstractJUnit4SpringContextTests的抽象事务扩展,它为JDBC访问添加了一些便利功能。此类要求在ApplicationContext中定义javax.sql.DataSource bean和PlatformTransactionManager bean。扩展AbstractTransactionalJUnit4SpringContextTests时,可以访问受保护的jdbcTemplate实例变量,该变量可用于运行SQL语句以查询数据库。你可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具结合使用时,请务必避免误报。正如JDBC测试支持中所提到的,AbstractTransactionalJUnit4SpringContextTests还提供了方便的方法,通过使用前面提到的jdbcTemplate委托给JdbcTestUtils中的方法。此外,AbstractTransactionalJUnit4SpringContextTests提供了一个executeSqlScript(..)方法,用于针对配置的DataSource运行SQL脚本。

这些类方便扩展。 如果你不希望将测试类绑定到特定于Spring的类层次结构,则可以使用@RunWith(SpringRunner.class)或Spring的JUnit规则来配置你自己的自定义测试类。

JUnit Jupiter的SpringExtension

Spring TestContext Framework提供了与JUnit 5中引入的JUnit Jupiter测试框架的完全集成。通过使用@ExtendWith(SpringExtension.class)注解测试类,你可以实现基于JUnit Jupiter的标准单元和集成测试,同时获得以下好处: TestContext框架,例如支持加载应用程序上下文,依赖注入测试实例,事务测试方法执行等。

此外,由于JUnit Jupiter中的丰富扩展API,Spring提供了以下功能,超出了Spring支持JUnit 4和TestNG的功能集:

  • 测试构造函数,测试方法和测试生命周期回调方法的依赖注入。有关更多详细信息,请参阅使用SpringExtension的依赖注入。

  • 强大支持基于SpEL表达式,环境变量,系统属性等的条件测试执行。有关更多详细信息和示例,请参阅Spring JUnit Jupiter测试注解中的@EnabledIf和@DisabledIf文档。

  • 自定义组合注解,结合了Spring和JUnit Jupiter的注解。有关详细信息,请参阅Meta-Annotation Support for Testing中的@TransactionalDevTestConfig和@TransactionalIntegrationTest示例。

以下代码清单显示了如何配置测试类以将SpringExtension与@ContextConfiguration结合使用:

// Instructs JUnit Jupiter to extend the test with Spring support.
@ExtendWith(SpringExtension.class)
// Instructs Spring to load an ApplicationContext from TestConfig.class
@ContextConfiguration(classes = TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

由于你还可以在JUnit 5中使用注解作为元注解,因此Spring提供了@SpringJUnitConfig和@SpringJUnitWebConfig组合注解,以简化测试ApplicationContext和JUnit Jupiter的配置。

以下示例使用@SpringJUnitConfig来减少上一个示例中使用的配置量:

// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load an ApplicationContext from TestConfig.class
@SpringJUnitConfig(TestConfig.class)
class SimpleTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

类似地,以下示例使用@SpringJUnitWebConfig创建WebApplicationContext以与JUnit Jupiter一起使用:

// Instructs Spring to register the SpringExtension with JUnit
// Jupiter and load a WebApplicationContext from TestWebConfig.class
@SpringJUnitWebConfig(TestWebConfig.class)
class SimpleWebTests {

    @Test
    void testMethod() {
        // execute test logic...
    }
}

有关更多详细信息,请参阅Spring JUnit Jupiter测试注解中的@SpringJUnitConfig和@SpringJUnitWebConfig的文档。

SpringExtension的依赖注入

SpringExtension从JUnit Jupiter实现了ParameterResolver扩展API,它允许Spring为测试构造函数,测试方法和测试生命周期回调方法提供依赖注入。

具体来说,SpringExtension可以将测试的ApplicationContext中的依赖项注入到使用@BeforeAll,@AfterAll,@BeforeEach,@AfterEach,@Test,@RequestTest,@ParameterizedTest等注解的测试构造函数和方法中。

构造函数注入

如果JUnit Jupiter测试类的构造函数中的参数是ApplicationContext类型(或其子类型),或者使用@Autowired,@Qualifier或@Value进行注解或元注解,则Spring会为该特定参数注入值使用来自test的ApplicationContext的相应bean。如果所有参数都应由Spring提供,你还可以使用@Autowired直接注解测试构造函数。

如果测试类的构造函数本身用@Autowired注解,则Spring承担解析构造函数中所有参数的责任。因此,没有向JUnit Jupiter注册的其他ParameterResolver可以解析此类构造函数的参数。

如果在测试方法之前或之后使用@DirtiesContext关闭测试的ApplicationContext,则不能将测试类的构造函数注入与JUnit Jupiter的@TestInstance(PER_CLASS)支持一起使用。

原因是@TestInstance(PER_CLASS)指示JUnit Jupiter在测试方法调用之间缓存测试实例。因此,测试实例将保留对最初从随后关闭的ApplicationContext注入的bean的引用。由于测试类的构造函数只会在这种情况下调用一次,因此依赖注入不会再次发生,后续测试将与关闭的ApplicationContext中的bean交互,这可能会导致错误。

要将@DirtiesContext与“测试前方法”或“测试后方法”模式结合使用@TestInstance(PER_CLASS),必须通过字段或setter注入来配置Spring的依赖关系,以便可以在测试之间重新注入它们方法调用。

在下面的示例中,Spring将从TestConfig.class加载的ApplicationContext中的OrderService bean注入OrderServiceIntegrationTests构造函数。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    private final OrderService orderService;

    @Autowired
    OrderServiceIntegrationTests(OrderService orderService) {
        this.orderService = orderService.
    }

    // tests that use the injected OrderService
}

请注意,此功能允许测试依赖项是final的,因此是不可变的。

方法注入

如果JUnit Jupiter测试方法或测试生命周期回调方法中的参数是ApplicationContext类型(或其子类型),或者使用@Autowired,@Qualifier或@Value进行注解或元注解,则Spring会为其注入值 来自test的ApplicationContext的相应bean的特定参数。

在下面的示例中,Spring将从TestConfig.class加载的ApplicationContext中的OrderService注入到deleteOrder()测试方法中:

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @Test
    void deleteOrder(@Autowired OrderService orderService) {
        // use orderService from the test's ApplicationContext
    }
}

由于JUnit Jupiter中的ParameterResolver支持的健壮性,你还可以将多个依赖项注入单个方法,不仅来自Spring,还来自JUnit Jupiter本身或其他第三方扩展。

以下示例说明如何同时将Spring和JUnit Jupiter注入依赖项放入placeOrderRepeatedly()测试方法。

@SpringJUnitConfig(TestConfig.class)
class OrderServiceIntegrationTests {

    @RepeatedTest(10)
    void placeOrderRepeatedly(RepetitionInfo repetitionInfo,
            @Autowired OrderService orderService) {

        // use orderService from the test's ApplicationContext
        // and repetitionInfo from JUnit Jupiter
    }
}

请注意,使用JUnit Jupiter中的@RepeatedTest可以让测试方法获得对RepetitionInfo的访问权限。

TestNG支持类

org.springframework.test.context.testng包为基于TestNG的测试用例提供以下支持类:

  • AbstractTestNGSpringContextTests

  • AbstractTransactionalTestNGSpringContextTests

AbstractTestNGSpringContextTests是一个抽象基础测试类,它将Spring TestContext Framework与TestNG环境中的显式ApplicationContext测试支持集成在一起。扩展AbstractTestNGSpringContextTests时,可以访问受保护的applicationContext实例变量,该变量可用于执行显式bean查找或测试整个上下文的状态。

AbstractTransactionalTestNGSpringContextTests是AbstractTestNGSpringContextTests的抽象事务扩展,它为JDBC访问添加了一些便利功能。此类要求在ApplicationContext中定义javax.sql.DataSource bean和PlatformTransactionManager bean。扩展AbstractTransactionalTestNGSpringContextTests时,可以访问受保护的jdbcTemplate实例变量,该变量可用于执行SQL语句以查询数据库。你可以在运行与数据库相关的应用程序代码之前和之后使用此类查询来确认数据库状态,Spring确保此类查询在与应用程序代码相同的事务范围内运行。与ORM工具结合使用时,请务必避免误报。正如JDBC测试支持中所提到的,AbstractTransactionalTestNGSpringContextTests还提供了方便的方法,通过使用上述的jdbcTemplate委托给JdbcTestUtils中的方法。此外,AbstractTransactionalTestNGSpringContextTests提供了一个executeSqlScript(..)方法,用于针对配置的DataSource运行SQL脚本。

这些类是便于扩展的。 如果你不希望将测试类绑定到特定于Spring的类层次结构,则可以使用@ContextConfiguration,@TestExecutionListeners等配置自己的自定义测试类,并使用TestContextManager手动检测测试类。 有关如何检测测试类的示例,请参阅AbstractTestNGSpringContextTests的源代码。

最后更新于