Spring:单元测试和集成测试

8
我希望您能为我提供使用Spring设置单元测试和集成测试的最佳实践方法。
我通常使用三种类型的测试:
1. “真正”的单元测试(没有依赖项) 2. 作为“单元”测试运行的测试(内存中的数据库,本地调用,模拟对象等),或者作为集成测试运行的测试(持久性数据库,远程调用等) 3. 仅作为集成测试运行的测试
目前,我只有第二类测试,这是比较棘手的部分。我会设置一个基础测试类,如下所示:
@ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

同时,“单元测试”应该像这样:

public class FooTest extends AbstractMyTestCase

使用自动装配属性。

如何在不同的(集成测试)环境中运行测试的最佳方法?子类化测试并覆盖ContextConfiguration?

@ContextConfiguration(locations = { "/my_spring_integration_test.xml" })
public class FooIntegrationTest extends FooTest

这个方案可行吗(我目前无法轻易在这里进行测试)?这种方法的问题在于 "@ContextConfiguration(locations = { "/my_spring_integration_test.xml" })" 重复出现很多次。
有什么建议吗?
问候, Florian

你找到适合你的解决方案了吗? - FrVaBe
4个回答

4
我扩展了GenericXmlContextLoader类,创建了一个名为MyContextLoader的类,并重写了protected String[] generateDefaultLocations(Class<?> clazz)方法,以收集我可以通过系统属性(-Dtest.config=)指定的目录中的配置文件名称。
我还修改了以下方法,以不修改任何位置。
@Override
protected String[] modifyLocations(Class<?> clazz, String... locations) {
    return locations;
}

我使用这个上下文加载器的方式如下:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = MyContextLoader.class)
public class Test { .... }

在运行测试时,使用指示配置文件来源的SystemProperty现在可以让您使用完全不同的配置。

当然,使用SystemProperty只是指定配置位置的策略之一。您可以在generateDefaultLocations()中进行任何操作。


编辑:

此解决方案使您可以使用完全不同的应用程序上下文配置(例如用于模拟对象),而不仅仅是不同的属性。您不需要构建步骤将所有内容部署到“类路径”位置。我的具体实现还将用户名称用作默认值以查找配置目录(src/test/resources/{user}),如果没有给出系统属性,则可以轻松维护项目上所有开发人员的特定测试环境。

仍然可以使用和推荐使用PropertyPlaceholder。


编辑:

Spring版本3.1.0将支持XML profiles/Environment Abstraction,这类似于我的解决方案,并将为不同的环境/配置文件提供选择。


这听起来很有趣。谢谢! - Puce
@Puce 请查看即将发布的Spring 3.1.0版本中的新功能(请参见我的最后一次编辑)。 - FrVaBe

2
我会选择这个版本:
ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

在我的Spring测试文件my_spring_test.xml中,我将使用PropertyPlaceHolderConfigurer机制。
JPA示例:
<context:property-placeholder
    system-properties-mode="OVERRIDE" 
    location="classpath:test.properties" />

<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close">
    <property name="driverClassName" value="${test.database.driver}" />
    <property name="url" value="${test.database.server}" />
    <property name="username" value="${test.database.user}" />
    <property name="password" value="${test.database.password}" />
</bean>

<bean id="entityManagerFactory"
    class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
    <property name="persistenceUnitName" value="test" />
    <property name="dataSource" ref="dataSource" />
    <property name="persistenceXmlLocation"
             value="classpath:META-INF/persistence.xml" />
    <property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
    <property name="showSql" value="false" />
    <property name="generateDdl" value="${test.database.update}" />
    <property name="database" value="${test.database.databasetype}" />
</bean>
    </property>
</bean>

现在你需要做的是,在类路径上为内存和真实集成测试准备不同版本的test.properties文件(当然,相应的驱动程序类也需要存在)。您甚至可以设置系统属性以覆盖属性值。
如果您想使用maven来准备此项工作,您会发现使用maven复制文件并不容易。您需要一种执行代码的方法,标准选择是maven-antrun-plugingmaven-maven-plugin
无论哪种方式:都有两个配置文件,例如在src/main/config中,并添加两个插件执行,一个在generate-test-resources阶段,另一个在pre-integration-test阶段。以下是GMaven版本:
<plugin>
    <groupId>org.codehaus.gmaven</groupId>
    <artifactId>gmaven-plugin</artifactId>
    <version>1.3</version>
    <executions>
        <execution>
            <phase>pre-integration-test</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
            <source>
            new File(
               pom.build.testOutputDirectory,
               "test.properties"
            ).text = new File(
                       pom.basedir,
                       "src/main/config/int-test.properties"
            ).text;
            </source>
            </configuration>
        </execution>
        <execution>
            <phase>generate-test-resources</phase>
            <goals>
                <goal>execute</goal>
            </goals>
            <configuration>
            <source>
            new File(
               pom.build.testOutputDirectory,
               "test.properties"
            ).text = new File(
                       pom.basedir,
                       "src/main/config/memory-test.properties"
            ).text;
            </source>
            </configuration>
        </execution>
    </executions>
</plugin>

是的,我已经在使用property-placeholder,并考虑替换类路径上的文件。使用Maven最简单的方法是什么?创建只包含单个属性文件的独立项目/依赖项,然后根据阶段/配置文件进行调整? - Puce
谢谢,虽然我暂时不想在项目中引入Groovy。我觉得问题在于Maven缺乏合适的集成测试支持:http://willcode4beer.com/opinion.jsp?set=maven2_integration-test 我会查看是否可以使用mave-antrun-pluging或者甚至是Maven资源插件(resources:copy-resources)来解决。 - Puce
你不是要在项目中引入Groovy,只是在构建过程中添加了两行Groovy代码。你的项目除了构建过程之外,不需要任何对Groovy的依赖(无需部署Jars)。同样地,在使用antrun时,你的项目也不需要对ant有任何依赖。 - Sean Patrick Floyd
嗯,看起来两个答案都可行,但只能选择一个作为正确答案...? - Puce
@Puce Groovy 的好处在于其源代码与 Java 95% 兼容。Groovy = Java + JDK 类的附加方法 + 一些语法糖。 - Sean Patrick Floyd
显示剩余2条评论

0

我在使用Spring 3.x的context:property-placeholder标签时没有成功。我使用了旧式的bean标签和一个属性文件,能够像下面这样在我的代码和数据库之间建立连接:

<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
    <property name="location" value="/com/my/package/database.properties"/>
</bean>


<bean id="myDatasource" class="oracle.ucp.jdbc.PoolDataSourceFactory" 
    factory-method="getPoolDataSource">
    <property name="URL" value="${JDBC_URL}"/>
    <property name="user" value="${JDBC_USERNAME}"/>
    <property name="password" value="${JDBC_PASSWORD}"/>
    <property name="connectionFactoryClassName"   
      value="oracle.jdbc.pool.OracleConnectionPoolDataSource"/>
    <property name="ConnectionPoolName" value="SCDB_POOL"/>
    <property name="MinPoolSize" value="5"/>
    <property name="MaxPoolSize" value="50"/>
    <property name="connectionWaitTimeout" value="30"/>
    <property name="maxStatements" value="100"/>
</bean>

这是一个属性文件的例子:
JDBC_URL=jdbc:oracle:thin:@myDB:1521:mySchema
JDBC_USERNAME=username
JDBC_PASSWORD=password

然后我像这样设置了我的JUnit测试:

@ContextConfiguration(locations = {"/com/my/pkg/test-system-context.xml"})
@RunWith(SpringJUnit4ClassRunner.class)
public class HeaderDaoTest {

    @Autowired
    HeaderDao headerDao;

    @Test
    public void validateHeaderId() {
        int headerId = 0;

        headerId = headerDao.getHeaderId();

        assertNotSame(0,headerId);
    }

}

这对我有用,但每个人的做法都有所不同。希望这能帮到你。


0

最近我遇到了同样的问题,并查看了@ContextConfiguration注释的文档,我注意到了inheritLocations选项。

通过将其添加到我的类中,例如

@ContextConfiguration(locations = { "/my_spring_integration_test.xml" }, inheritLocations=false)
public class FooIntegrationTest extends FooTest

我发现我可以根据需要覆盖ContextConfiguration。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接