Spring:单元和集成测试

发布于 2024-10-10 22:04:08 字数 869 浏览 2 评论 0 原文

我正在寻找使用 Spring 设置单元和集成测试的最佳实践。

我通常使用 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”})”重复很多。

有什么建议吗?

问候, 弗洛里安

I'm looking for best practices for setting up unit and integration tests using Spring.

I usually use 3 kind of tests:

  • "real" unit tests (no dependencies)
  • tests run either as "unit" test (in-memory db, local calls, mock
    objects,...) or as integration test
    (persistent db, remote calls,...)
  • tests run only as integration tests

Currently I only have tests of the second category, which is the tricky part.
I set-up a base test class like:

@ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

And "unit" tests like:

public class FooTest extends AbstractMyTestCase

with autowired attributes.

What's the best way to run the test in a different (integration test) environment? Subclass the test and override the ContextConfiguration?

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

Would this work (I cannot currently easily test it here)? The problem with this approach is that "@ContextConfiguration(locations = { "/my_spring_integration_test.xml" })" is duplicated a lot.

Any suggestions?

Regards,
Florian

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

攒一口袋星星 2024-10-17 22:04:08

我扩展了 GenericXmlContextLoader

public class MyContextLoader extends GenericXmlContextLoader {

并重写

protected String[]generateDefaultLocations(Class> clazz)

方法来收集 a 的配置文件名我可以通过 SystemProperty (-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 配置文件/环境抽象 这与我的解决方案类似,可以为不同的环境/配置文件选择配置文件。

I extended the GenericXmlContextLoader

public class MyContextLoader extends GenericXmlContextLoader {

and overrote the

protected String[] generateDefaultLocations(Class<?> clazz)

method to collect the config file names of a directory which I can specify by a SystemProperty (-Dtest.config=).

I also modified the follwowing method to NOT modify any locations

@Override
protected String[] modifyLocations(Class<?> clazz, String... locations) {
    return locations;
}

I use this context loader like this

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(loader = MyContextLoader.class)
public class Test { .... }

Running the test with a SystemProperty indicating the source of the config files enables you now to use completely different configurations.

The usage of a SystemProperty is of course only one strategy to specify the configuration location. You can do whatever you want in generateDefaultLocations().


EDIT:

This solution enables you to use complete different application context configurations (e.g. for mock objects) and not only different properties. You do not need a build step to deploy everything to your "classpath" location. My concrete implementation also used the users name as default to look for a configuration directory (src/test/resources/{user}) if no system property is given (makes it easy to maintain specific test environments for all developers on the project).

The usage of the PropertyPlaceholder ist still possible and recommended.


EDIT:

Spring Version 3.1.0 will support XML profiles/Environment Abstraction which is similar to my solution and will enable the choice of configuration files for different environments/profiles.

莳間冲淡了誓言ζ 2024-10-17 22:04:08

我会选择这个版本:

ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

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-plugin< /a> 和 gmaven-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>

I'd go with this version:

ContextConfiguration(locations = { "/my_spring_test.xml" })
public abstract class AbstractMyTestCase extends AbstractJUnit4SpringContextTests

and in my_spring_test.xml, I'd use the PropertyPlaceHolderConfigurer mechanism.

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

Now all you need to do is have different versions of test.properties on the class path for in-memory and real integration tests (and of course the respective driver classes need to be present). You can even set system properties to overwrite the property values.


If you want to prepare this with maven, you will find that copying files with maven is not trivial. You will need a way to execute code, the standard choices being the maven-antrun-plugin and gmaven-maven-plugin.

Either way: have two configuration files, e.g. in src/main/config and add two plugin executions, one in phase generate-test-resources and one in phase pre-integration-test. Here's the GMaven version:

<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>
少跟Wǒ拽 2024-10-17 22:04:08

我没有成功使用 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);
    }

}

然后我像这样 对我来说,但每个人做事的方式都略有不同。希望这有帮助。

I have had no success in using Spring 3.x context:property-placeholder tag. I have used the old fashion bean tag along with a properties file and was able to set up a connection between my code and my database like so:

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

Here's an example of the properties file:

JDBC_URL=jdbc:oracle:thin:@myDB:1521:mySchema
JDBC_USERNAME=username
JDBC_PASSWORD=password

Then I set up my JUnit test like so:

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

}

That worked for me, but everybody does things a little differently. Hope this helps.

西瓜 2024-10-17 22:04:08

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

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

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

我发现我能够根据需要覆盖 ContextConfiguration。

I recently ran in to the same problem and looking at the documentation for the @ContextConfiguration annotation, I noticed the inheritLocations option.

By adding this to my class e.g.

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

I found that I was able to override the ContextConfiguration as desired.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文