在单元测试中使用随机性和/或迭代?

时间:2009-04-23 10:08:25

标签: unit-testing

在单元测试中,我已经习惯了测试应用一些常规值的方法,一些冒犯方法合同的值,以及我能想到的所有边界情况。

但这是非常糟糕的做法

  • 对随机值进行测试,这是一个您认为永远不会给出任何麻烦的范围内的值,因此每次测试运行时,都会传入另一个值?作为一种对常规值的广泛测试?
  • 使用迭代测试整个范围?

我觉得这两种方法都没有任何好处。通过范围测试,我可以想象,这样做是不切实际的,因为它需要时间,但随机性?

更新:

我自己并没有使用这种技术,只是想知道它。如果你能在需要的时候将随机性变成可重现的,随机性可以成为一个很好的工具 最有趣的回答是列文的“模糊”提示:

http://en.wikipedia.org/wiki/Fuzz_testing

TX

11 个答案:

答案 0 :(得分:5)

单元测试需要快速。如果他们不是人们将不会定期运行他们。有时我会检查整个范围的代码,但@ Ignore'd最后评论了它,因为它使测试太慢了。如果我要使用随机值,我会选择带有固定种子的PRNG,这样每次运行都会检查相同的数字。

答案 1 :(得分:5)

  1. 随机输入 - 测试不会可重复(每次运行时产生一致的结果,因此不被视为良好的单元测试。测试不应改变主意。
  2. 范围测试/ RowTests - 只要它们不会减慢测试套件运行速度就很好..每个测试都应该尽可能快速运行。 (30秒内完成测试套件的运行频率超过10分钟) - 最好是100毫秒或更短。那说每个输入(测试数据)应该是'代表'输入。如果所有输入值都相同,则测试每个输入值不会添加任何值,而只是常规数字运算。您只需要该组值中的一个代表。您还需要代表边界条件和“特殊”值。
  3. 有关指南或大纲的更多信息,请参阅'What makes a Good Unit Test?'

    那就是说...你提到的技术对找到代表性输入很有用。所以用它们来找到代码失败或成功错误的scenarioX - 然后写出一个可重复的,快速的测试仅针对该scenarioX进行单件测试,并将其添加到测试套件中。如果您发现这些工具继续帮助您找到更多优秀的测试用例,请坚持下去。

    对OP澄清的回应:

    • 如果在每次测试运行中对随机无生成器使用相同的种子值(测试输入),则测试不是随机的 - 可以预先确定值。但是,理想情况下,单元测试不需要任何输入/输出 - 这就是xUnit测试用例具有void TC()签名的原因。
    • 如果您在每次运行中使用不同的种子值,那么现在您的测试是随机的且不可重复。当然,您可以查找日志文件中的特殊种子值,以了解失败的原因(并重现错误)但我喜欢我的测试,以便立即让我知道失败的原因 - 例如Red TestConversionForEnums()让我知道枚举转换代码在没有任何检查的情况下被破坏。

    可重复 - 意味着每次在SUT上运行测试时,它都会产生相同的结果(通过/失败)..不能“我能再次重现测试失败吗?” (可重复!=可重现)。重申..这种探索性测试可能很好地识别更多测试用例,但我不会将其添加到我每次在白天进行代码更改时运行的测试套件中。我建议手动进行探索性测试,找到一些好的(有些人可能会使用虐待狂)测试人员会在你的代码中使用锤子和钳子..会发现你比测试用例更多的测试用例。

答案 2 :(得分:3)

您所描述的内容通常称为基于规范的测试,并且已由QuickCheck(Haskell),scalacheck(Scala)和Quviq QuickCheck(Erlang)等框架实现。

基于数据的测试工具(例如TestNG中的DataProvider)可以获得类似的结果。

基本原则是根据某种规范为被测对象生成输入数据,远非“不良做法”。

答案 3 :(得分:3)

我一直在我的测试用例中使用随机性。它在SUT中发现了一些错误,它在我的测试用例中给了我一些错误。

请注意,使用randomnes会使测试用例变得更复杂。

  • 您需要一种方法来运行您的测试用例,其中包含失败的随机值
  • 您需要记录每次测试使用的随机值。
  • ...

总而言之,我正在重新使用随机性而不是将其排除在外。与每种技术一样,它具有其价值。

为了更好地解释您的目标,请查找术语fuzzing

答案 4 :(得分:2)

你在测试什么?随机数发生器?还是你的代码?

如果您的代码,如果代码中存在产生随机数的错误怎么办?

如果您需要重现某个问题,您是否继续重新启动测试,希望它最终会使用与您发现问题时相同的序列?

如果您决定使用随机数生成器生成数据,至少使用已知的常量值对其进行播种,因此很容易重现。

换句话说,你的“随机数字”只是一个“数字序列,我真的不在乎”。

答案 5 :(得分:1)

只要它以某种方式告诉你它失败的随机值,我不认为它是那么糟糕。但是,您几乎可以依靠运气在您的应用程序中找到问题。

测试整个范围将确保您涵盖所有途径,但是当您覆盖边缘并且我认为有一些中间接受的值时,它似乎有点过分。

答案 6 :(得分:1)

单元测试的目标是让您对代码充满信心。因此,如果您感觉使用随机值可以帮助您找到更多错误,那么您显然需要更多测试来提高您的置信度。

在这种情况下,您可以依靠基于迭代的测试来识别这些问题。 我建议为循环测试发现的案例创建新的特定测试,然后删除基于迭代的测试;这样他们就不会减慢你的测试速度。

答案 7 :(得分:1)

我使用随机性来调试状态机泄漏资源时的字段问题。我们对代码进行了检查,运行了单元测试,无法重现泄漏。

我们将整个可能的事件空间中的随机事件提供给状态机单元测试环境。我们在每个事件发生后查看不变量,并在违反事件时停止。

随机事件最终暴露了一系列导致泄漏的事件。 当第一个错误从第一个错误中恢复时,状态机在发生第二个错误时泄漏了资源。

然后我们能够重现现场泄漏。

所以随机性发现了一个很难找到的问题。有点蛮力,但计算机并不介意周末工作。

答案 8 :(得分:0)

我不会提倡完全随机的值,因为它会给你一种虚假的安全感。如果你不能通过整个范围(通常是这种情况),那么手动选择子集会更有效率。这样,您还必须考虑可能的“奇数”值,这些值会导致代码以不同方式运行(并且不在边缘附近)。

您可以使用随机生成器生成测试值,检查它们是否代表一个好的样本,然后使用它们。这是一个好主意,特别是如果手动选择太费时间。

当我编写一个信号量驱动程序用于两个不同芯片的hw块时,我确实使用了随机测试值。在这种情况下,我无法弄清楚如何为时间选择有意义的值,所以我随机化了芯片(独立地)尝试访问块的频率。回想起来,手动选择它们仍然会更好,因为让测试环境工作的方式使得两个芯片不能自我对齐并不像我想象的那么简单。这实际上是随机值不创建随机样本的一个很好的例子。

这个问题是由于每当另一个芯片保留了该块时,另一个芯片就等待了,而另一个芯片在另一个芯片发布后立即获得了访问权限。当我绘制芯片必须等待访问多长时间时,这些值实际上远非随机。最糟糕的是,当我对两个随机值都有相同的值范围时,在我将它们更改为具有不同的范围后,它稍微好一点,但它仍然不是很随机。我开始接受随机测试之后,我将访问之间的等待时间随机化,保留了这个块多长时间并仔细选择了这四个。

最后,我可能最终花了更多时间编写代码来使用“随机”值,而不是用来手动选择有意义的值。

答案 9 :(得分:0)

见David Saff关于Theory-Based Testing的工作。

一般来说,我会避免单元测试中的随机性,但理论上的东西很有趣。

答案 10 :(得分:0)

这里的'关键'点是单元测试。如果种子是常数,那么在回归测试中,预期范围内的大量随机值以及良好情况的边缘和不良情况的边界/边界是有价值的。

单位测试可以使用预期范围内的随机值,如果可以始终保存输入/输出(如果有)之前和之后。