使用DBUnit,Spring和TestNG进行数据库单元测试

闻人鸿飞
2023-12-01

我真的很喜欢Spring,因此我倾向于充分利用它的功能。 但是,在其哲学的某些黑暗角落,我倾向于不同意其某些假设。 一种这样的假设是数据库测试的工作方式。 在本文中,我将解释如何配置您的项目,以使Spring TestDBUnit在多开发人员环境中协同工作。

语境

我的基本需求是能够测试一些复杂的查询:在进行集成测试之前,我必须验证那些查询是否可以为我提供正确的结果。 这些本质上不是单元测试但是让我们将它们关联起来。 为了实现这一点,我使用了一个名为DBUnit的框架。 尽管自2010年下半年以来未进行维护,但我还没有找到替代者(请成为我的提案客人)。

我也有一些限制:

  • 我想对所有测试类都使用TestNG,以便新开发人员不会考虑使用哪种测试框架。
  • 我希望能够使用Spring Test,以便可以将测试依赖项直接注入到测试类中
  • 我希望能够在任何测试结束时亲自查看数据库状态,以便在出现问题时执行自己的查询以查找原因
  • 我希望每个开发人员都有自己的隔离数据库实例/方案

考虑到最后一点,我们的组织让我们为每个“单元测试”从每个开发人员一个Oracle模式中受益。

基本设置

Spring提供了开箱即用的AbstractTestNGSpringContextTests类。 反过来,这意味着我们可以将TestNG注释以及@Autowired应用于子类。 这也意味着我们可以访问底层的applicationContext ,但是我不愿意(而且在任何情况下都不需要)。

这种测试的结构如下所示:

@ContextConfiguration(location="classpath:persistence-beans.xml")
publicclassMyDaoTestextendsAbstractTestNGSpringContextTests{

    @Autowired
    privateMyDaomyDao;

    @Test
    publicvoidwhenXYZThenTUV(){
        ...
    }
}

熟悉Spring和TestNG的读者在这里不要感到惊讶。

引入DBunit

DbUnit是针对数据库驱动项目的JUnit扩展,除其他事项外,它使数据库在测试运行之间进入已知状态。 […] DbUnit能够与XML数据集之间导出和导入数据库数据。 从2.0版开始,在流式模式下使用DbUnit时,它也可以处理非常大的数据集。 DbUnit还可以帮助您验证数据库数据是否与预期的一组值匹配。

DBunit是一个JUnit扩展,应该扩展提供的父类org.dbunit.DBTestCase 。 在我的上下文中,我必须重新定义一些设置和拆卸操作才能使用Spring继承层次结构。 幸运的是,DBUnit开发人员考虑了这一点并提供了相关文档

在可用的不同策略中,我的爱好分别是在设置和拆卸时倾向于CLEAN_INSERT和NONE操作。 这样,如果测试失败,我可以直接检查数据库状态。 这样更新了我的测试类:

@ContextConfiguration(locations={"classpath:persistence-beans.xml","classpath:test-beans.xml"})
publicclassMyDaoTestextendsAbstractTestNGSpringContextTests{

    @Autowired
    privateMyDaomyDao;

    @Autowired
    privateIDatabaseTesterdatabaseTester;

    @BeforeMethod
    protectedvoidsetUp()throwsException{
        // Get the XML and set it on the databaseTester
        // Optional: get the DTD and set it on the databaseTester
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.setTearDownOperation(DatabaseOperation.NONE);
        databaseTester.onSetup();
    }

    @Test
    publicvoidwhenXYZThenTUV(){
        ...
    }
}

使用Spring按用户配置

当然,我们需要有一个特定的Spring配置文件来注入databaseTester 。 例如,这是一个:

<?xml version="1.0" encoding="UTF-8"?>
<beansxmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
        <beanclass="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
            <propertyname="location"value="${user.name}.database.properties"/>
        </bean>
        <beanname="dataSource"class="org.springframework.jdbc.datasource.SingleConnectionDataSource">
             <propertyname="driverClass"value="oracle.jdbc.driver"/>
             <propertyname="username"value="${db.username}"/>
             <propertyname="password"value="${db.password}"/>
             <propertyname="url"value="jdbc:oracle:thin:@<server>:<port>/${db.schema}"/>
        </bean>
        <beanname="databaseTester"class="org.dbunit.DataSourceDatabaseTester">
            <constructor-argref="dataSource"/>
        </bean>
</beans>

然而,不只是眼神。 注意,必须向databaseTester提供数据源。 由于要求每个开发人员都拥有一个数据库,因此基本上有两种选择:使用内存数据库或使用与生产环境相同的数据库,并为每个开发人员提供一个这样的数据库架构。 我倾向于后一种解决方案(如果可能),因为它倾向于减小测试环境和生产环境之间的差异。

因此,为了使每个开发人员都可以使用自己的模式,我使用了Spring在运行时替换Java系统属性的功能:每个开发人员都具有不同的user.name来表征。 然后,我配置一个PlaceholderConfigurer来查找{user.name}.database.properties文件,如下所示:

db.username=myusername1
db.password=mypassword1
db.schema=myschema1

这使我可以实现每个开发人员使用自己的Oracle实例的目标。 如果要使用此策略,请不要忘记为Continuous Integration Server提供特定的database.properties。

哦?

最后,整个测试链都被配置到数据库层。 但是,当运行先前的测试时,一切正常(或不正常),但是在检查数据库时,它看起来没有任何变化。 奇怪的是,如果您确实加载了一些XML数据集并在测试过程中对其进行了断言,则它的行为也相应地:这带有事务问题的所有症状。 实际上,当您仔细查看Spring的文档时,一切都会变得清晰。 Spring的愿景是,与DBUnit完全矛盾的是,运行测试应该使数据库保持不变。 默认情况下,只需在测试结束时回滚所有更改即可实现此目的。

为了更改此行为,唯一要做的就是使用@TransactionConfiguration(defaultRollback=false)注释测试类。 请注意,这不会阻止我们使用@Rollback批注指定特定方法,这些方法应视情况而定,不会影响数据库状态。

测试类变为:

@ContextConfiguration(locations={"classpath:persistence-beans.xml","classpath:test-beans.xml"})
@TransactionConfiguration(defaultRollback=false)
publicclassMyDaoTestextendsAbstractTestNGSpringContextTests{

    @Autowired
    privateMyDaomyDao;

    @Autowired
    privateIDatabaseTesterdatabaseTester;

    @BeforeMethod
    protectedvoidsetUp()throwsException{
        // Get the XML and set it on the databaseTester
        // Optional: get the DTD and set it on the databaseTester
        databaseTester.setSetUpOperation(DatabaseOperation.CLEAN_INSERT);
        databaseTester.setTearDownOperation(DatabaseOperation.NONE);
        databaseTester.onSetup();
    }

    @Test
    publicvoidwhenXYZThenTUV(){
        ...
    }
}

结论

尽管关于数据库测试的Spring和DBUnit视图是反对的,但是Spring的配置多功能性使我们使其能够满足我们的需求(以及DI的好处)。 当然,其他改进也是可能的:在父级测试类中推送通用代码,等等。

翻译自: https://blog.frankel.ch/database-unit-testing-with-dbunit-spring-and-testng/

 类似资料: