Spring 3.1 M2:@Configuration 类和配置文件的测试

工程 | Sam Brannen | 2011年6月21日 | ...

正如 Jürgen Höller 在他的文章中提到的那样,该文章宣布了Spring 3.1 M2 的发布Spring TestContext 框架(*) 进行了全面修改,以便为@Configuration类和环境配置文件提供一流的测试支持。

在这篇文章中,我将首先引导您完成一些演示这些新测试功能的示例。然后,我将介绍 TestContext 框架中的一些新扩展点,这些扩展点使这些新功能成为可能。

      请注意:这是我公司博客www.swiftmind.com的交叉发布。

背景

在 Spring 2.5 中,我们引入了 *Spring TestContext 框架*,它提供基于注解的集成测试支持,可与 JUnit 或 TestNG 一起使用。此博客中的示例将重点介绍基于 JUnit 的测试,但此处使用的所有功能也适用于 TestNG。

TestContext 框架的核心是允许您使用@ContextConfiguration注解测试类,以指定用于为测试加载ApplicationContext的配置文件。默认情况下,ApplicationContext 使用GenericXmlContextLoader加载,该加载器从 XML Spring 配置文件加载上下文。然后,您可以通过使用@Autowired@Resource@Inject注解测试类中的字段来访问ApplicationContext中的 bean。

Spring 3.0 通过@Configuration类引入了对基于 Java 的配置的支持,但是 TestContext 框架直到现在还没有提供合适的ContextLoader来支持测试中的@Configuration类。Spring 3.1 M2 为此目的引入了一个新的AnnotationConfigContextLoader,并且@ContextConfiguration注解已更新为通过新的classes属性支持声明@Configuration类。

现在让我们来看一些示例。

使用基于 XML 的配置进行集成测试

Spring 参考手册的测试章节提供了许多关于如何使用 XML 配置文件配置集成测试的示例,但我们将在此处包含一个示例作为快速介绍。

如果您已经熟悉 Spring TestContext 框架,可以跳到下一节。


<?xml version="1.0" encoding="UTF-8"?>
<beans ...>

    <!-- this bean will be injected into the OrderServiceTest class -->
    <bean id="orderService" class="com.example.OrderServiceImpl">
        <!-- set properties, etc. -->
    </bean>
    
    <!-- other beans -->

</beans>

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from "classpath:/com/example/OrderServiceTest-context.xml"
@ContextConfiguration
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

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

在前面的示例中,我们配置 JUnit 使用SpringJUnit4ClassRunner运行我们的测试。我们使用 JUnit 的@RunWith注解来实现这一点。我们还使用 Spring 的@ContextConfiguration注解注释我们的测试类,而无需指定任何属性。在这种情况下,将使用默认的GenericXmlContextLoader,并遵循 *约定优于配置* 的原则,Spring 将从classpath:/com/example/OrderServiceTest-context.xml加载我们的ApplicationContext。在testOrderService()方法中,我们可以直接测试使用@Autowired注入到测试实例中的OrderService。请注意,orderServiceOrderServiceTest-context.xml中定义为一个 bean。

使用 @Configuration 类进行集成测试

Spring 3.1 M2 对使用@Configuration类进行集成测试的支持类似于上面的基于 XML 的示例。因此,让我们重新编写该示例以使用@Configuration类和新的AnnotationConfigContextLoader


package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the static inner ContextConfiguration class
@ContextConfiguration(loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {

    @Configuration
    static class ContextConfiguration {

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

此示例与基于 XML 的示例之间存在一些显着差异

  1. 没有 XML 文件。
  2. bean 定义已使用静态内部ContextConfiguration类中的@Configuration@Bean从 XML 转换为 Java。
  3. AnnotationConfigContextLoader已通过@ContextConfigurationloader属性指定。

否则,测试的配置和实现保持不变。

那么,Spring 如何知道使用静态内部ContextConfiguration类来加载ApplicationContext呢?答案是 *约定优于配置*。默认情况下,如果没有显式声明类,AnnotationConfigContextLoader将查找测试类的名为ContextConfiguration的静态内部类。根据@Configuration类的要求,此静态内部类必须是非 final 的且非私有的。

注意:从 Spring 3.1 M2 开始,默认配置类必须准确命名为ContextConfiguration。但是,从 Spring 3.1 RC1 开始,命名限制已被取消。换句话说,从 RC1 开始,您可以选择将默认配置类命名为您想要的任何名称,但其他要求仍然适用。

在下面的示例中,我们将看到如何声明显式配置类。


package com.example;

@Configuration
public class OrderServiceConfig {

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

package com.example;

@RunWith(SpringJUnit4ClassRunner.class)
// ApplicationContext will be loaded from the OrderServiceConfig class
@ContextConfiguration(classes=OrderServiceConfig.class, loader=AnnotationConfigContextLoader.class)
public class OrderServiceTest {

    @Autowired
    private OrderService orderService;

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

我们现在已将静态内部ContextConfiguration类提取到名为OrderServiceConfig的顶级类中。为了指示AnnotationConfigContextLoader使用此配置类而不是依赖于默认配置类,我们只需通过@ContextConfiguration的新classes属性声明OrderServiceConfig.class。与@ContextConfigurationlocations属性(用于资源位置)一样,我们可以通过向classes属性提供一个 Class[] 数组来声明多个配置类——例如:@ContextConfiguration(classes={Config1.class, Config2.class}, ... )

这就结束了对使用@Configuration类进行集成测试的介绍。现在让我们来看看 Spring 对环境配置文件的测试支持。

使用环境配置文件进行集成测试

正如 Chris Beams 在他关于Spring 3.1 M1 发布的发布公告以及他的后续博客@Profile 的介绍中所讨论的那样,Spring 3.1 在框架中引入了一流的支持,用于环境和配置文件(也称为 *bean 定义配置文件*)的概念。从 Spring 3.1 M2 开始,集成测试也可以配置为激活特定 bean 定义配置文件以用于各种测试场景。这是通过使用新的@ActiveProfiles注解注释测试类并提供应在加载测试的ApplicationContext时激活的配置文件列表来实现的。

注意:@ActiveProfiles可与新的SmartContextLoader SPI 的任何实现一起使用(参见后面的讨论),但@ActiveProfiles**不支持**更简单的ContextLoader SPI 的实现。

让我们来看一些使用 XML 配置和@Configuration类的示例。


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

package com.bank.service;

@RunWith(SpringJUnit4ClassRunner.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时,它的ApplicationContext将从类路径根目录中的app-config.xml配置文件加载。如果您检查app-config.xml,您会注意到accountRepository bean 依赖于dataSource bean;但是,dataSource未定义为顶级 bean。相反,dataSource定义了两次:一次在 *生产* 配置文件中,一次在 *开发* 配置文件中。

通过使用@ActiveProfiles("dev")注解TransferServiceTest,我们指示 Spring TestContext 框架加载ApplicationContext,并将活动配置文件设置为 {"dev"}。结果,将创建一个嵌入式数据库,并且accountRepository bean 将与开发 DataSource 的引用连接起来。这很可能是我们在集成测试中想要的!

以下代码清单演示了如何实现相同的配置和集成测试,但使用@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
	public DataSource dataSource() throws Exception {
		Context ctx = new InitialContext();
		return (DataSource) ctx.lookup("java:comp/env/jdbc/datasource");
	}
}

@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(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader=AnnotationConfigContextLoader.class,
    classes={TransferServiceConfig.class, StandaloneDataConfig.class, JndiDataConfig.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

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

有关如何简化上述@Configuration类的详细信息,请参阅Spring 3.1 M1:介绍 @Profile博客文章。

ApplicationContext 缓存

从 Spring 2.5 开始,*Spring TestContext 框架*已缓存集成测试的ApplicationContexts,其基于为给定测试生成的键,该键是从所有合并的上下文资源位置生成的。由于ContextLoader SPI 只支持位置,因此此密钥生成算法足以唯一标识用于加载ApplicationContext的配置。但是,随着对配置类和配置文件的支持增加,旧算法已不再足够。

因此,Spring 3.1 M2 更新了上下文缓存键生成算法,其中包含以下所有内容:

  • 位置(来自@ContextConfiguration
  • (来自@ContextConfiguration
  • contextLoader (来自@ContextConfiguration
  • activeProfiles (来自@ActiveProfiles

这对开发者意味着您可以实现一个声明特定资源位置或配置类的基测试类。然后,如果您想针对该基配置运行测试,但使用不同的活动配置文件,您可以扩展该基测试类,并使用@ActiveProfiles注释每个具体子类,为每个子类提供一组不同的配置文件进行激活。因此,每个子类都将定义一组唯一的配置属性,这将导致加载和缓存不同的ApplicationContexts

SmartContextLoader 取代 ContextLoader SPI

正如本文前面提到的,Spring 3.1 M2 引入了一个新的SmartContextLoader SPI,它取代了现有的ContextLoader SPI。如果您计划开发或已经开发了自己的自定义ContextLoader,您可能需要仔细查看新的SmartContextLoader接口。与旧的ContextLoader接口相比,SmartContextLoader可以处理资源位置和配置类。此外,SmartContextLoader可以在其加载的上下文中设置活动的bean定义配置文件。

ContextLoader将继续得到支持,任何现有的该SPI实现都应该继续按原样工作;但是,如果您想在自定义加载器中支持配置类或环境配置文件,则需要实现SmartContextLoader

DelegatingSmartContextLoader

如果您一直密切关注到目前为止提供的示例,您可能已经注意到,当使用配置类时,我们总是必须为@ContextConfigurationloader属性显式声明AnnotationConfigContextLoader.class。但是,当我们指定XML配置文件(或依赖于约定优于配置)时,GenericXmlContextLoader将默认使用。

如果Spring能够注意到我们使用的是配置类还是XML资源位置,然后自动选择合适的ContextLoader来加载我们的应用程序上下文,岂不是很好?

是的,我们也这么认为!;)

因此,对于Spring 3.1 RC1,我们计划引入一个DelegatingSmartContextLoader,它将委托给候选SmartContextLoaders列表(即GenericXmlContextLoaderAnnotationConfigContextLoader),以确定哪个上下文加载器适合给定测试类的配置。然后将使用获胜的候选者来实际加载上下文。

这项工作完成后,DelegatingSmartContextLoader将取代GenericXmlContextLoader成为默认加载器。您可以随意关注JIRA中此项开发的进度:SPR-8387

总结

Spring 3.1 为@Configuration类和环境配置文件提供了一流的测试支持,我们鼓励您尽快尝试这些功能。M2 是3.1 版本发布系列中的最后一个里程碑。因此,如果您发现任何错误或有任何改进建议,现在是采取行动的时候了!


(*) 参考手册尚未更新以反映对@Configuration类和环境配置文件的测试支持,但这些功能肯定会在Spring 3.1 RC1或GA中得到很好的记录。与此同时,每个新类和注释的JavaDoc可以作为良好的起点。

获取Spring通讯

与Spring通讯保持联系

订阅

领先一步

VMware 提供培训和认证,以加快您的进度。

了解更多

获取支持

Tanzu Spring在一个简单的订阅中提供OpenJDK™、Spring和Apache Tomcat®的支持和二进制文件。

了解更多

即将举行的活动

查看Spring社区中所有即将举行的活动。

查看全部