数据库单元测试框架?

Posted

技术标签:

【中文标题】数据库单元测试框架?【英文标题】:DB Unit Testing framework? 【发布时间】:2011-10-19 22:37:45 【问题描述】:

在我的项目中,我使用了 spring、jpa 和 PostgreSQL DB, 我在数据库中有很多表,我需要对所有表进行单元测试。

是否有任何框架可以在每次测试完成后回滚所有事务,以便每次测试都有新的/相同的数据库数据进行测试。这样,在所有测试执行之后,数据库模式的数据就会保持原样。

对此有何建议?

我对 DBUnit 有所了解,但我需要为每个测试的每个输入数据编写 .xml 文件,并且需要在 setup() 中插入数据并在 tearDown() 中清除/删除数据,但不需要对我来说似乎是更好的策略。

感谢任何建议。 谢谢。

【问题讨论】:

【参考方案1】:

正如@Ryan 所指出的......应该咨询Testing section of the Spring Reference manual。

一些启动技巧...

我们已经使用 Spring 的 AbstractTransactionalJUnit4SpringContextTests 处理了这个问题。

例如,我们定义一个抽象超类:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("file:WebContent/WEB-INF/testconfig/test-web-application-config.xml")
@TransactionConfiguration()
@Transactional
public abstract class OurAbstractTransactionalSpringContextTest extends AbstractTransactionalJUnit4SpringContextTests 

然后需要额外上下文的各个子类被定义为:

@ContextConfiguration("classpath:path/to/config/ConfigForTestCase.xml")
public class TestOurFunction extends OurAbstractTransactionalSpringContextTest 
    @Test
    public void testOurMethod() 
    


注意:

    并非所有测试类都需要额外的上下文,请跳过特定子类上的 @ContextConfiguration。 我们通过 ant 执行并在 junit 任务上使用 forkmode="perBatch" 属性。这确保了所有测试都使用相同的上下文配置运行(避免为每个测试重新加载 Spring 上下文)。您可以使用@DirtiesContext 指示在方法/类之后应刷新上下文。 用@Test 注释标记每个方法。 Spring 框架不会使用 Junit 的 public void testXXX() 约定来选择方法。

【讨论】:

【参考方案2】:

是否有任何框架可以在每次测试完成后回滚所有事务,以便每次测试都有新的/相同的数据库数据进行测试。这样,在所有测试执行之后,数据库模式的数据就会保持原样。

来自当天早些时候发布的my other answer,是的,这可以使用 DbUnit。 (根据您的编辑,您不需要这个;我的答案的后续部分说明了我使用 DbUnit 的原因以及何时不使用它)。

以下代码 sn-p 演示了如何执行每个测试的设置:

@Before
public void setUp() throws Exception

    logger.info("Performing the setup of test ", testName.getMethodName());
    IDatabaseConnection connection = null;
    try
    
        connection = getConnection();
        IDataSet dataSet = getDataSet();
        //The following line cleans up all DbUnit recognized tables and inserts and test data before every test.
        DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
    
    finally
    
        // Closes the connection as the persistence layer gets it's connection from elsewhere
        connection.close();
    


private IDatabaseConnection getConnection() throws Exception

    @SuppressWarnings( "rawtypes", "unused" )
    Class driverClass = Class.forName("org.apache.derby.jdbc.ClientDriver");
    Connection jdbcConnection = DriverManager.getConnection(jdbcURL, "XXX",
            "YYY");
    IDatabaseConnection databaseConnection = new DatabaseConnection(jdbcConnection);
    return databaseConnection;


private IDataSet getDataSet() throws Exception

    ClassLoader classLoader = this.getClass().getClassLoader();
    return new FlatXmlDataSetBuilder().build(classLoader.getResourceAsStream("database-test-setup.xml"));

database-test-setup.xml 文件包含将为每次测试插入数据库的数据。在setup 方法中使用DatabaseOperation.CLEAN_INSERT 可确保文件中指定的所有表都将被清除(通过删除所有行),然后将指定数据插入到测试数据文件中。

避免使用 DbUnit

我使用上述方法专门在每次测试开始之前清除序列,因为应用程序使用 JPA 提供程序在单独的事务中更新序列。如果您的应用程序没有做任何类似的事情,那么您只需在 setup() 方法中启动事务并在测试后在拆卸时发出回滚。如果我的应用程序不使用序列(并且如果我不想重置它们),那么我的设置例程将非常简单:

@Before
public void setUp() throws Exception

    logger.info("Performing the setup of test ", testName.getMethodName());
    // emf is created in the @BeforeClass annotated method
    em = emf.createEntityManager();
    // Starts the transaction before every test
    em.getTransaction.begin();


@After
public void tearDown() throws Exception

    logger.info("Performing the teardown of test ", testName.getMethodName());
    if (em != null)
    
        // Rolls back the transaction after every test
        em.getTransaction().rollback();
        em.close();
    

另外,我在 Maven 中使用 dbdeploy,但这主要是为了使测试数据库与版本化数据模型保持同步。

【讨论】:

感谢您的回复,它真的很有帮助,但我认为第一种方法(DbUnit)存在数据插入的开销,因为它在 setup() 期间为每个测试完成,所以例如可以有很多测试我们需要一个/两个表中的数据,但每次 setup() 都会为所有表插入数据。 我认为第二种方法(事务)不会将数据保存在数据库中(它不会对数据进行实际持久性),因此数据库不会抛出约束违规异常,并且似乎不适用于所有场景.如果我错了,请告诉我。 @SmartSolution,您始终可以为每个灯具拥有单独的 XML 文件。当然,如果夹具中的每个测试只需要一部分数据并且这是开销,那么我建议使用Testcase Class per Fixture pattern。关于约束违反和引发异常的主题,据我所知,当您刷新 PersistenceContext 而不是在 COMMIT 时将引发持久性异常。 @SmartSolution,如果您不刷新持久性上下文,那么请考虑更改您的存储库/DAO 类来这样做。如果您有一组单元测试将在普通 Java SE 环境中创建持久性上下文并测试您的 DAO/存储库行为,则可以避免这种情况 - 这不会测试事务传播等。【参考方案3】:

Spring's test framework 正是为你做的。

【讨论】:

这是正确的,但不是很具体。请参阅 Jacob 的详细说明。【参考方案4】:

我会按照以下方式处理它。

当项目处于测试模式时。我使用引导数据来测试使用dbdeploy 可以断言的固定数据。并直接使用dao 来测试您的应用程序的DAO 和DB 层。

希望对你有帮助

更新

例如,您的系统中有一个名为Person 的实体,现在您可以在此测试的是基本的 CRUD 操作。

运行引导数据脚本来加载数据 从 DB 中检索所有人员并对其进行断言。同样明智地查看所有 CRUD

要回滚你可以标记的事务

@TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true)

所以它会回滚数据库的东西

【讨论】:

通过这种方式,由于数据未在数据库中提交,因此不会测试数据的持久性,并且可能不会抛出一些违反约束等异常,因此这种与事务相关的方法似乎不是正确的策略适用于所有场景。 你的意思是说,不提交trasaction就会抛出约束冲突异常?如果是的话,我真的不知道这个,你能给出一些想法吗? @JigarJoshi 让我们continue this discussion in chat

以上是关于数据库单元测试框架?的主要内容,如果未能解决你的问题,请参考以下文章

单元测试02

单元测试基本框架和8个测试方面

单元测试基本框架和8个测试方面

第六章:单元测试框架unittest

单元测试框架unittest

单元测试框架选择