【问题标题】:Unit-Testing: Database set-up for tests单元测试:用于测试的数据库设置
【发布时间】:2021-11-10 03:53:54
【问题描述】:

我正在为使用数据库的应用程序编写单元测试,我希望能够针对一些示例/测试数据运行应用程序 - 但我不确定设置测试的初始测试数据。

我正在寻找的是一种针对我当前在调试时使用的同一数据库(或示意性相同)运行被测代码的方法 - 在每次测试之前,我想确保数据库在插入测试数据之前重置为干净的状态。

我意识到使用 IRepository 模式可以让我消除针对实际数据库进行测试的复杂性,但我不确定在我的情况下这是否可行。

有什么建议或文章可以为我指明正确的方向吗?

谢谢!

--编辑--

谢谢大家,这些是一些很好的建议!我可能会走模拟我的数据访问层的路线,结合一些简单的设置类来准确生成每次测试所需的数据。

【问题讨论】:

    标签: c# database unit-testing


    【解决方案1】:

    这是我尝试使用的一般方法。我设想了大约三到四个级别的测试:单元测试、交互测试、集成测试、验收测试。

    在单元测试级别,它只是代码。任何数据库交互都被模拟出来,无论是手动还是使用流行的框架之一,因此加载数据不是问题。它们运行得很快,并确保对象按预期工作。这允许非常快速的写测试/写代码/运行测试周期。模拟对象提供每个测试所需的数据。

    交互测试测试非平凡类交互的交互。同样,不需要数据库,它被模拟出来了。

    现在在集成级别,我正在测试组件的集成,这就是真正的数据库、队列、服务、yada yada 投入其中的地方。如果可以的话,我将使用一种流行的内存数据库,所以初始化不是问题。它总是从空的开始,我使用实用程序类来清理数据库并在每次测试之前准确加载我想要的数据,这样测试之间就没有耦合。

    我在使用内存数据库时遇到的问题是它们通常不支持我需要的所有功能。例如,也许我需要一个外连接,而内存数据库不支持它。在这种情况下,我通常会再次针对 MySQL 等本地传统数据库进行测试,并在每次测试之前对其进行清理。由于应用在单独的环境中部署到生产环境中,因此测试周期不会触及这些数据。

    【讨论】:

    • 擦洗类是什么样的?是否找到最近的主键 ID 并删除相关记录?
    • 不,实际上,它完全删除了所有内容,并加载了我测试所需的内容。
    【解决方案2】:

    我发现处理此问题的最佳方法是使用包含已知数据的静态测试数据库,并使用事务来确保您的测试不会改变任何内容。

    在您的测试设置中,您将启动一个事务,在您的测试清理中,您将回滚事务。这使您可以修改测试中的数据,但也可以确保在测试完成时一切都恢复到原始状态。

    【讨论】:

    • 如果您不提交事务,您怎么知道您的更改有效?
    • @Robert - 在进行更新之后(但在回滚之前),您的测试将执行任何必要的检查以验证更新是否正确发生。由于检查与更新在同一个事务中执行,因此即使更新尚未提交,它们也应该看到更新。
    • 根据您的想法,事务应该在测试中,而不是在您测试的方法中。
    • @dynback.com - 是的,这是正确的,尽管如果您使用 TransactionScope 之类的东西,测试和方法本身都可以有自己的事务处理。
    • 如果使用事务,则无法查看测试失败时存储在DB中的数据
    【解决方案3】:

    我知道您使用的是 C#,但在 Java World 中有 Spring 框架。它允许您在事务中运行数据库微计算,并在此事务之后回滚此事务。这意味着您可以在测试完成后对真实数据库进行操作而无需触及状态。也许这可能是进一步研究 C# 的提示。

    【讨论】:

    • 如果我需要测试几个事务函数,我怎么知道它们工作,如果我不能提交?
    • 我目前正在为 NHibernate 使用内存中的 SQLite。这是集成和单元测试之间的有趣分离。这是一种将 ORM 与其数据库隔离开来的方法,而且似乎很有效。我通过这种方式发现了很多错误。
    【解决方案4】:

    Mocking 是对代码进行单元测试的最佳方式。

    就集成测试而言,我在使用 SQLite 等内存数据库时遇到了一些问题,主要是因为行为和/或语法上的微小差异。

    我一直在使用 MySql 的本地实例在几个项目中进行集成测试。返回的问题是服务器设置和测试数据的创建。 我创建了一个名为 Mysql.Server 的小型 Nuget 包(请参阅 https://github.com/stumpdk/MySql.Server 了解更多信息),它只是在每次运行测试时设置一个 MySql 的本地实例。

    通过运行此实例,您可以轻松地为测试设置表结构和示例数据,而无需担心您的生产环境或本地服务器设置。

    【讨论】:

      【解决方案5】:

      我不认为有一个简单的方法来完成这个。您只需要创建那些 Pre-Test sql 设置脚本和 post-test Tear-down 脚本。然后,您需要为每次运行触发这些脚本。很多人建议使用 SQLLite 进行单元测试设置。

      【讨论】:

      • SQLLite 与大型数据库有很多不同,所以没有必要对其进行测试
      【解决方案6】:

      我发现最好让我的测试转到不同的数据库,这样我就可以把它擦干净并放入我想要用于测试的数据。

      您可能希望数据库可以在程序中设置,然后您的测试可以告诉类更改数据库。

      【讨论】:

        【解决方案7】:

        此代码清除 MS SQL Server 中所有用户表中的所有数据:

        private DateTime _timeout;
        
        public void ClearDatabase(SqlConnection connection)
        {
            _timeout = DateTime.Now + TimeSpan.FromSeconds(30);
            do
            {
                SqlCommand command = connection.CreateCommand();
                command.CommandText = "exec sp_MSforeachtable 'DELETE FROM ?'";
                try
                {
                    command.ExecuteNonQuery();
                    return;
                }
                catch (SqlException)
                {
                }
            } while (!TimeOut());
        
            if (TimeOut())
                Assert.Fail("Fail to clear DB");
        }
        
        private bool TimeOut()
        {
            return DateTime.Now > _timeout;
        }
        

        【讨论】:

        • 我想不太好。您必须以正确的顺序运行它们,否则违反约束会引发异常
        【解决方案8】:

        如果您正在考虑真正的数据库使用,那么我们很可能在这里讨论集成测试。即测试,将应用行为作为不同组件的组合来检查,这与单元测试相反,单元测试应该单独测试组件。

        定义了测试范围后,我不建议像其他作者建议的那样使用内存数据库或模拟库之类的东西。问题是内存数据库的行为通常略有不同或功能集有所减少,并且根本没有模拟数据库,因此您将测试一般意义上的其他应用程序,而不是您将要测试的应用程序正在向您的客户交付。

        我宁愿建议通过仅覆盖逻辑的关键部分而将其余部分用于单元测试来最小化集成测试的数量,同时使用设置尽可能接近生产环境的真实数据库。如果有很多集成,测试运行可能会太慢而且真的很痛苦。

        您还可以使用一些技巧来优化测试执行的速度:

        • 根据它们引入的数据突变将测试拆分为读取和写入,并并行运行前一个测试,无需任何清理。 (例如,如果被测系统是 webapp 并且测试更像是端到端的,那么 HTTP GET 请求可以安全地并行运行);
        • 对所有数据使用唯一的插入/删除脚本并尽可能优化。您可能会发现我目前正在开发的 Reseed 库很有帮助。它能够为您生成插入和删除脚本。所以基本上你所要求的。或查看Respawn 可用于数据库清理;
        • 使用数据库快照进行还原,这可能比完整的插入/删除周期更快;
        • 将每个测试包装在事务中,然后再将其还原(这也不是 100% 诚实的,而且有些脆弱);
        • 使用数据库池而不是唯一的数据库池来并行化您的测试。 Docker 和TestContainers 可能适合这里;

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-01-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-12-30
          • 2015-09-16
          • 1970-01-01
          相关资源
          最近更新 更多