【问题标题】:Unit testing with random data使用随机数据进行单元测试
【发布时间】:2012-07-18 04:18:26
【问题描述】:

我读过在单元测试中生成随机数据通常是一个坏主意(我明白为什么),但是测试随机数据然后从随机测试构造一个固定单元测试用例发现的错误似乎不错。但是我不明白如何很好地组织它。我的问题实际上与特定的编程语言或特定的单元测试框架无关,所以我将使用 python 和一些伪单元测试框架。这是我对它的编码方式:

def random_test_cases():
   datasets = [
       dataset1,
       dataset2,
       ...
       datasetn
   ]
   for dataset in datasets:
       assertTrue(...)
       assertEquals(...)
       assertRaises(...)
       # and so on

问题是:当这个测试用例失败时,我无法确定是哪个数据集导致失败。我看到了两种解决方法:

  1. 为每个数据集创建一个测试用例 — 问题在于测试用例负载和代码重复。
  2. 通常测试框架允许我们将消息传递给断言函数(在我的示例中,我可以执行类似assertTrue(..., message = str(dataset)) 的操作)。问题是我应该将这样的消息传递给每个断言,这看起来也不优雅。

有没有更简单的方法?

【问题讨论】:

  • 数据集是静态的还是随机生成的?
  • @zerkms 我稍微澄清了这个问题。首先,我随机生成大数据集,如果发现错误,我会将这样的测试添加到固定的静态数据集中。
  • @karlicoss:您似乎希望使用单元测试框架来做Fuzz testing。这并不奏效,因为模糊测试需要数千台机器以高吞吐量进行数百万次测试。数据不是完全随机的:它是根据被测软件所期望的精细数据结构生成的,以最大限度地提高命中率。这需要一种不同类型的框架,目前还没有流行的框架。 (模糊测试本身就是一个小众领域,没有多少普通程序员会使用。)

标签: unit-testing testing tdd


【解决方案1】:

在 R 的快速检查中,我们尝试如下解决此问题

  • 测试实际上是伪随机的(种子是固定的),因此您始终可以重现您的测试结果(当然,排除外部因素)
  • test 函数返回足够的数据来重现错误,包括失败的断言和导致失败的数据。在test 的返回值上调用的便利函数repro 将使您在失败的断言开始时进入调试器,并将参数设置为失败的见证人。如果测试以批处理模式执行,则等效信息存储在文件中,检索它的命令打印在 stderr 中。然后你可以像以前一样拨打repro。无论您是否使用 R 编程,我都想知道这是否开始满足您的要求。此解决方案的某些方面可能难以在动态性较低或没有一流功能的语言中实现。

【讨论】:

    【解决方案2】:

    我也认为这是个坏主意。

    请注意,不要在代码中抛出随机数据,而是让单元测试这样做。这一切都归结为为什么你首先要进行单元测试。答案是“驱动代码的设计”。随机数据不会驱动代码的设计,因为它依赖于非常严格的公共接口。请注意,您可以使用它找到错误,但这不是单元测试的目的。请注意,我说的是单元测试,而不是一般的测试。

    话虽如此,我强烈建议您查看QuickCheck。它是 Haskell,所以它在演示上有点狡猾,在文档上有点像博士,但你应该能够弄清楚。不过,我将总结一下它是如何工作的。

    在您选择要测试的代码(比如sort() 函数)之后,您可以建立应该保持的不变量。在这个例子中,如果result = sort(input):,你可以有以下不变量。

    • result 中的每个元素都应小于或等于下一个元素。
    • input 中的每个元素都应在 result 中出现相同的次数。
    • resultinput 应该有相同的长度(这是重复前面的,但让我们用它来说明)。

    您将每个变体编码在一个简单的函数中,该函数获取结果和输出并检查这些不变量是否编码。

    然后,您告诉 QuickCheck 如何生成input。由于这是 Haskell 并且类型系统很糟糕,它可以看到该函数接受一个整数列表并且它知道如何生成这些整数。它基本上生成随机整数和随机长度的随机列表。当然,如果您有更复杂的数据类型(例如,只有正整数、只有平方等),它可以更细粒度。

    最后,当您拥有这两个时,您只需运行 QuickCheck。它随机生成所有这些东西并检查不变量。如果有些失败,它会告诉你究竟是哪些。它还会告诉您随机种子,因此您可以在需要时重新运行这个确切的失败。作为额外的奖励,每当它获得失败的不变量时,它会尝试将输入减少到不满足不变量的最小可能子集(如果您考虑树结构,它会将其减少到不满足不变量的最小子树)。

    你有它。在我看来,这就是你应该如何用随机数据测试东西。这绝对不是单元测试,我什至认为你应该以不同的方式运行它(例如,让 CI 不时运行它,而不是在每次更改时运行它(因为它会很快变慢))。让我再说一遍,这与单元测试有不同的好处 - QuickCheck 发现错误,而单元测试驱动设计。

    【讨论】:

      【解决方案3】:

      只要您选择正确的断言方法,单元测试框架通常支持“信息失败”。

      但是,如果其他一切都不起作用,您可以轻松地将数据集跟踪到控制台/输出文件。技术含量低,但应该可以。

      [TestCaseSource("GetDatasets")]
      public Test.. (Dataset d)
      {
         Console.WriteLine(PrettyPrintDataset(d));
         // proceed with checks
         Console.WriteLine("Worked!");
      }
      

      【讨论】:

        【解决方案4】:

        我仍然认为这是个坏主意。

        单元测试需要简单明了。给定相同的代码和相同的单元测试,您应该能够无限运行它并且永远不会得到不同的响应,除非有外部因素在起作用。与此相反的目标会增加自动化的维护成本,这与目标背道而驰。

        在维护方面之外,对我来说似乎很懒惰。如果您考虑到自己的功能并了解正面和负面的测试用例,那么开发单元测试就很简单了。

        我也不同意显示如何在同一个测试用例中执行多个测试用例的用户。当测试失败时,您应该能够立即判断哪个测试失败并知道失败的原因。测试应该尽可能简单,并且尽可能简洁/与被测代码相关。

        【讨论】:

        • 但这是发现您可能错过的事情的好方法。然后,您可以继续为它们添加适当的单元测试。我认为 OP 也将其用作辅助技术..而不是主要技术。
        • @nick-stinemates 它们很简单,一旦添加到单元测试用例中,它们是固定的而不是随机的(或带有固定种子的随机以避免插入千字节的代码)。有时有些错误只能通过随机压力测试才能发现。但是,将这样的随机数据集减少到仍然会出现错误的小数据集可能是个好主意(实际上您可能会在调试时这样做)。但无论如何,那么问题是«如何在不重复代码的情况下以相同的方式测试一堆测试»。
        • 在 R 包快速检查中,我们尝试通过在开始生成输入数据之前为随机数生成器播种一个常数来解决您的第一个反对意见(缺乏可重复性)。
        【解决方案5】:

        您可以通过扩展而不是枚举来定义测试,或者您可以从一个案例中调用多个测试案例。

        从单个测试用例调用多个测试用例:

        MyTest()
        {
            MyTest(1, "A")
            MyTest(1, "B")
            MyTest(2, "A")
            MyTest(2, "B")
            MyTest(3, "A")
            MyTest(3, "B")
        }
        

        有时使用一些测试框架可以通过优雅的方式来实现这一点。 Here 在 NUnit 中是怎么做的:

        [Test, Combinatorial]
        public void MyTest(
            [Values(1,2,3)] int x,
            [Values("A","B")] string s)
        {
            ...
        }
        

        【讨论】:

          猜你喜欢
          • 2010-09-23
          • 2014-02-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-04-01
          • 2016-07-03
          • 2021-09-25
          • 2019-10-26
          相关资源
          最近更新 更多