【问题标题】:Database cleanup after Junit testsJunit 测试后的数据库清理
【发布时间】:2010-08-05 07:25:55
【问题描述】:

我必须使用 Junit 测试一些 Thrift 服务。当我作为 Thrift 客户端运行测试时,服务会修改服务器数据库。我找不到一个可以在每次测试运行后清理数据库的好解决方案。 清理很重要,尤其是因为 ID 需要是唯一的,这些 ID 当前是从 XML 文件中读取的。现在,我必须在运行测试后手动更改 ID,以便下一组测试可以运行而不会在数据库中引发主键冲突。如果我可以在每次测试运行后清理数据库,那么问题就完全解决了,否则我将不得不考虑其他解决方案,例如生成随机 ID 并在需要 ID 的地方使用它们。

编辑:我想强调一下,我正在测试一个正在写入数据库的服务,我没有直接访问数据库的权限。但既然服务是我们的,我可以根据需要修改服务以提供任何清理方法。

【问题讨论】:

    标签: java database unit-testing junit


    【解决方案1】:

    如果您使用的是 Spring,那么您需要的只是测试类上的 @DirtiesContext 注解。

    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration("/test-context.xml")
    @DirtiesContext(classMode = ClassMode.AFTER_EACH_TEST_METHOD)
    public class MyServiceTest {
       ....
    }
    

    【讨论】:

    • DirtiesContext 是关于 Spring 上下文的再创造——这是一个耗时的操作,所以不是好方法
    • 对于任何实际项目来说,这都不是一个好的解决方案。它将为每次测试启动 Spring Boot 应用程序。经过几次测试课程后,需要几分钟才能完成,并且会大大减慢开发速度。您希望测试快速,以便尽可能频繁地运行它们以避免破坏代码。
    【解决方案2】:

    除非您测试特定的数据库操作(例如验证您可以查询或更新数据库),否则您的 JUnit 不应该写入真实的数据库。相反,您应该模拟数据库类。这样您实际上不必连接和修改数据库,因此不需要清理。

    您可以通过几种不同的方式模拟您的课程。您可以使用诸如JMock 之类的库,它将为您完成所有执行和验证工作。我个人最喜欢的方法是使用依赖注入。通过这种方式,我可以创建实现我的存储库接口的模拟类(您正在为数据访问层使用接口,对吗?;-))并且我只实现具有已知操作/返回值的所需方法。

    //Example repository interface.
    public interface StudentRepository
    {
       public List<Student> getAllStudents();
    }
    
    //Example mock database class.
    public class MockStudentRepository implements StudentRepository
    {
       //This method creates fake but known data.
       public List<Student> getAllStudents()
       {
          List<Student> studentList =  new ArrayList<Student>();
          studentList.add(new Student(...));
          studentList.add(new Student(...));
          studentList.add(new Student(...));
    
          return studentList;
       }
    }
    
    //Example method to test.
    public int computeAverageAge(StudentRepository aRepository)
    {
       List<Student> students = aRepository.GetAllStudents();
       int totalAge = 0;
       for(Student student : students)
       {
          totalAge += student.getAge();
       }
    
       return totalAge/students.size();
    }
    
    //Example test method.
    public void testComputeAverageAge()
    {
       int expectedAverage = 25; //What the expected answer of your result set is
       int actualAverage = computeAverageAge(new MockStudentRepository());
    
       AssertEquals(expectedAverage, actualAverage);
    }
    

    【讨论】:

    • "Unless you as testing specific database actions (verifying you can query or update the database for example) your JUnits shouldn't be writing to a real database." 对此没有统一的共识。在一个阵营中,我们有一些人认为应该模拟数据库,另一方面,我们也有一些人认为应该针对真实数据库执行所有数据库测试,最好是与生产数据库匹配的数据库。
    • 对不起,我对这些东西有点陌生,在 flex 中编码有一段时间了。您能否详细说明我需要做什么。我不确定你所说的模拟类和不修改数据库是什么意思。通常,我运行的测试会创建新客户并使用该客户来创建订单和其他内容。如果不进入数据库,所有这些数据会去哪里?
    • 当您需要检索数据时,您创建一个类并实现您需要返回所需数据的方法。如果您需要保存数据,您可以创建一个类并实现 save(无论它叫什么)方法。此方法可以仅将数据存储在实例变量中。如果您需要保存和检索,那么这些方法只需要从同一个实例变量中读取和写入。
    【解决方案3】:

    使用DBUnit 之类的东西怎么样?

    【讨论】:

      【解决方案4】:

      Spring 的单元测试框架具有广泛的处理 JDBC 的能力。一般的方法是单元测试在事务中运行,并且(在您的测试之外)一旦测试完成,事务就会回滚。

      这样做的好处是能够使用您的数据库及其架构,但无需对数据进行任何直接更改。当然,如果你真的在测试中执行了提交,那么所有的赌注都没有了!

      如需了解更多信息,请查看Spring's documentation,了解使用 JDBC 进行集成测试。

      【讨论】:

        【解决方案5】:

        在编写 JUnit 测试时,您可以覆盖两个特定方法:setUp() 和 tearDown()。在 setUp() 中,您可以设置测试代码所需的一切,因此您不必在每个特定的测试用例中进行设置。在所有测试用例运行后调用 tearDown()。

        如果可能,您可以设置它,以便您可以在 setUp() 方法中打开数据库,然后清除测试中的所有内容并在 tearDown() 方法中关闭它。这就是我们在拥有数据库时完成所有测试的方式。

        这是一个例子:

        @Override
        protected void setUp() throws Exception {
            super.setUp();
            db = new WolfToursDbAdapter(mContext);
            db.open();
        
            //Set up other required state and data
        }
        
        @Override
        protected void tearDown() throws Exception {
            super.tearDown();
            db.dropTables();
            db.close();
            db = null;
        }
        
        //Methods to run all the tests
        

        【讨论】:

          【解决方案6】:

          假设您可以访问数据库:另一种选择是在测试之前创建数据库的备份,并在测试之后从该备份恢复。这可以自动化。

          【讨论】:

          • 是的,我可以访问数据库,这也是我在与几位同事交谈后打算做的事情。谢谢约翰。
          • @Manfred 你能告诉我怎么做吗?
          • @AhmedHussein 一般来说,数据库服务器都有命令行客户端,允许创建数据库备份或从备份中恢复数据库。您可以将这些步骤添加到自动执行测试的脚本中。在测试开始之前,您可以删除现有数据库并使用测试所需的内容恢复数据库。这样,您的测试将始终有一组定义明确的数据作为开始。如果您的数据包含沿时间轴的数据,则可能存在一些问题。在这种情况以及可能的其他情况下,您可能需要额外的步骤来为测试套件准备数据库。
          【解决方案7】:

          如果您使用的是 Spring + Junit 4.x,那么您不需要在 DB 中插入任何内容。 看着 AbstractTransactionalJUnit4SpringContextTests 班级。

          还可以查看 Spring 文档以获取 JUnit 支持。

          【讨论】:

            【解决方案8】:

            这有点苛刻,但我通常的目标是在每次测试方法执行之前清除数据库(或只是我感兴趣的表)。当然,当我进入更多集成类型的测试时,这往往不起作用。

            在我无法控制数据库的情况下,假设我想验证在给定调用之后创建的行数是否正确,那么测试将计算测试调用前后的行数,并确保区别是正确的。换句话说,考虑现有数据,然后查看测试代码如何改变事物,而不假设现有数据的任何内容。设置起来可能需要一些工作,但让我测试一个更“实时”的系统。

            在您的情况下,具体的 ID 重要吗?您能否即时生成 ID(可能是随机生成),验证它们尚未在使用中,然后继续?

            【讨论】:

              【解决方案9】:

              如果您尝试针对从数据库中提取的数据进行测试,我同意 Brainimus 的观点。如果您要测试对数据库所做的修改,另一种解决方案是模拟数据库本身。您可以使用多种内存数据库实现来创建临时数据库(例如在 JUnit 的 setUp() 期间),然后从内存中删除整个数据库(在 tearDown() 期间)。只要您不使用特定于供应商的 SQL,那么这是测试修改数据库而不触及实际生产数据库的好方法。

              一些提供内存支持的优秀 Java 数据库是 Apache DerbyJava DB(但它又是 Oracle 的 Apache Derby)、HyperSQL(更广为人知的 HSQLDB)和H2 Database Engine。我个人使用 HSQLDB 创建内存模拟数据库进行测试,效果很好,但我相信其他人会提供类似的结果。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2014-04-07
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多