【问题标题】:How to write Unit Tests for functions that rely on dynamic data?如何为依赖动态数据的函数编写单元测试?
【发布时间】:2012-05-04 12:14:57
【问题描述】:

假设您有一个网站,它使用一个函数从数据库中检索数据并返回要显示/解析/等的结果...

由于从数据库中检索到的数据是动态的,并且可能在一天中的每一秒都发生变化,您如何正确地为该函数编写单元测试?

假设该函数应该返回一个结果数组。显然,单元测试可以测试是否返回数组。但是,当由于 MySQL 查询编写错误导致数组本身的内容不正确时会发生什么?数组的大小可能为零,或者数组的内容可能不正确。由于它依赖于不断变化的数据,单元测试如何知道什么是正确的,什么不是?是否需要从单元测试本身调用数据库,以便与它进行比较?

如何为依赖动态数据的函数正确编写单元测试?

【问题讨论】:

标签: database unit-testing dynamic-data


【解决方案1】:

理想形式的单元测试应该只测试一件事。在这种情况下,您正在测试两件事:

  1. 函数的逻辑
  2. 数据库检索

所以我建议进行以下重构:

  1. 将数据库检索逻辑移到单独的函数中
  2. 让您要测试的函数调用其他函数
  3. 模拟返回数据的函数,以便您可以对应用程序的逻辑进行单元测试
  4. 如果有意义(如果您只是依赖另一个库来执行此操作,那么希望该库已经进行了测试),为动态检索功能编写一个单元测试,您无法测试细节,但可以可以测试返回数据的结构和合理性(比如所有字段都设置好了,是现在5秒以内的时间)。

此外,通常最好在测试环境中运行单元测试,您可以完全控制数据库中存储的内容。您不想针对生产数据运行这些。

【讨论】:

  • 好点。所以你是说我们应该只测试我们的数据检索功能的逻辑,而不是检索到的数据是否正确?此外,在我们的开发环境中,我们通常对我们的实时数据库 -> 开发数据库进行单向同步,以便我们使用最新数据。无论如何,数据仍然是动态的。谢谢。
  • 在单元级别上,我倾向于测试我写入数据库的逻辑是否正确,以及我的读取逻辑是否正确。我不需要针对数据库对 sql 进行单元测试,除非我使用的是我构建的一些自定义 ORM。然后,我通常会编写功能测试或集成测试,以证明我的数据符合我的预期。对除了测试之外没有任何更新的数据库执行这些操作。使用动态数据模拟真实场景对于集成(和性能)测试来说很好,但对系统的信心应该来自其他系统。
  • 如何模拟一个函数?
  • @RodrigoRuiz 在这种情况下,模拟是指使用假实现存根函数定义的过程(并可能测试它是否使用正确的参数调用以使其成为真正的模拟,而不仅仅是存根 - 见martinfowler.com/articles/mocksArentStubs.html)。如何实际模拟一个函数取决于您使用的语言和测试框架,如果您找不到有关您选择的语言/框架的大量信息,这将是一个很好的 StackOverflow 问题。
【解决方案2】:

如果您的函数除了从数据库中提取数据之外还做了一些有趣的事情,您应该将检索结果提取到不同的函数中并模拟它,这样您就可以测试其余部分。

这仍然留给您测试数据库访问的任务。您不能真正为此进行单元测试,因为根据定义,它不会访问任何数据库,您可以只测试它是否发送您认为应该发送的 sql 语句,但不能测试 sql 语句是否真的有效。

所以你需要一个数据库

您有多种选择:

1) 为此类测试创建一个不会被测试更改的固定数据库。

Pro:概念上很简单 缺点:难以维护。测试变得相互依赖,因为它们依赖于相同的数据。无法测试更新、插入或删除的东西(更不用说 DDL)

2) 在测试期间创建一个数据库。现在你有两个问题:为测试设置数据库并用数据填充它。

设置:

1) 有一个正在运行的数据库服务器,为需要运行测试的每个人(至少 devs + ci-server)提供一个用户/模式/数据库。 Schema 可以使用 hibernate 之类的东西或用于部署的脚本来创建。

效果很好,但会让老式的 DBA 发疯。应用程序不得依赖于模式名称。当应用程序使用多个模式时,您也会遇到问题。这个设置相当慢。它可以帮助放入快速光盘。像 RAM 盘

2) 有一个内存数据库。从代码开始很容易,而且速度很快。但在大多数情况下,它的行为与您的生产数据库相同。如果你使用一些试图隐藏差异的东西,这就不那么重要了。我经常在第一个构建阶段使用内存数据库,在第二个阶段使用真实数据库。

加载测试数据

1) 人们告诉我使用 dbunit。我不相信当列或约束发生变化时,它似乎有很多 XML 并且难以维护。

2) 我更喜欢普通的应用程序代码。 (Java + Hibernate)在我的情况下,但是在生产中将数据写入数据库的代码在许多情况下应该适合为您的测试编写测试数据。有一个特殊的 API 来隐藏满足所有外键和东西的细节会有所帮助:http://blog.schauderhaft.de/2011/03/13/testing-databases-with-junit-and-hibernate-part-1-one-to-rule-them/

【讨论】:

    【解决方案3】:

    你不能,真的。您需要有保证的静态数据集来创建可靠的单元测试。也许数据库快照对您有用。

    动态数据在其他方面也很有用,例如执行回归测试...

    【讨论】:

      【解决方案4】:

      大多数测试都侧重于获取数据等涉及的逻辑路径。不是数据本身的有效性。数据有效性只有在您的应用程序以某种方式计算或聚合数据或其他情况下才有意义,在这种情况下,您应该能够控制输入并验证结果是否正确。

      也就是说,有时您确实希望访问您的应用用来验证退货的同一数据库。例如,如果您正在测试一个返回过滤数据集的函数,您的单元测试可以执行相同的查询,然后对每个记录的主键进行逐行比较,并验证您的函数是否返回您期望的相同数据集。

      我不知道这是否是您的具体问题,但相反,在单元测试中访问数据库以执行断言并没有错。至少我一直这样做,没有人试图逮捕我:)

      【讨论】:

        【解决方案5】:

        忽略您在谈论数据库这一事实,我认为您可能正在寻找您的单元测试来涵盖所有可能导致收益递减的情况。如果我是你,我会介绍一条标准路径,然后介绍几个边缘情况。事实是你不能务实地测试一切。

        这里有一些进一步的阅读

        http://37signals.com/svn/posts/3159-testing-like-the-tsa
        How deep are your unit tests?
        http://johnnosnose.blogspot.co.uk/2012/04/re-over-testing.html
        http://martinfowler.com/bliki/TestCoverage.html

        查看您的特定 db 相关问题,要测试此功能,您可能需要创建一个 seam 来预填充数据,以便涵盖这些情况。

        【讨论】:

        • 呵呵 根据我的经验,大多数错误恰好出现在难以测试的领域——因此,这些似乎是唯一真正受益于测试框架的时间投资的领域。我敢肯定,经理们看到了这一点,并认为,“我们只需要对 x、y z 进行更多的单元测试”,而实际上“x”、“y”和“z”本质上是由几乎不可测试的特性定义的。由于这些原因,我认为单元测试几乎不比最严格、非敏捷的基于 UML 的 OOP 实践灵活。作为一个按小时工作的独立开发人员,这确实是一项灵丹妙药。
        【解决方案6】:

        我会在测试本身中构建数据。这样,您甚至可以针对不断变化的数据测试复杂的场景。关键是您可以通过拥有一个专用的测试数据库来控制测试中的数据变化,

        第1步:将您需要的数据插入到仅供测试使用的数据库中 第 2 步:数据库现在处于稳定的可预测状态,因此您可以运行查询并测试输出

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2015-05-30
          • 1970-01-01
          • 1970-01-01
          • 2016-01-19
          • 1970-01-01
          • 2016-10-21
          • 2017-02-15
          相关资源
          最近更新 更多