使用随机数据进行单元测试

时间:2012-07-19 02:24:53

标签: unit-testing testing tdd

我读过在单元测试中生成随机数据通常是一个坏主意(我明白为什么),但测试随机数据然后从随机测试构建固定单元测试用例发现错误似乎很好。但是我不明白如何很好地组织它。我的问题实际上与特定的编程语言或特定的单元测试框架无关,所以我将使用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))的事情)。问题是我应该将这样的消息传递给每个断言,这看起来也不优雅。
  3. 有更简单的方法吗?

5 个答案:

答案 0 :(得分:6)

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

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

在维修方面,对我来说似乎很懒散。如果您考虑到您的功能并理解正面和负面测试用例,那么开发单元测试很简单。

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

答案 1 :(得分:2)

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

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

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)
{
    ...
}

答案 2 :(得分:2)

我也认为这是一个坏主意。

请注意,不要在代码中丢弃随机数据,而是让单元测试执行此操作。这一切都归结为为什么你首先进行单元测试。答案是“推动代码设计”。随机数据不会驱动代码的设计,因为它依赖于非常严格的公共接口。请注意,你可以找到它的bug,但这不是单元测试的内容。请注意,我说的是单元测试,而不是一般的测试。

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

选择要测试的代码(假设为sort()函数)后,建立应该保留的不变量。在此示例中,如果result = sort(input):。

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

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

然后,告诉QuickCheck如何生成input。由于这是Haskell并且类型系统踢了屁股,它可以看到该函数采用整数列表并且它知道如何生成这些。它基本上生成随机整数和随机长度的随机列表。当然,如果你有一个更复杂的数据类型(例如,只有正整数,只有正方形等),它可以更精细。

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

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

答案 3 :(得分:0)

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

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

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

答案 4 :(得分:0)

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

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