单元测试中的随机数据?

时间:2008-08-28 14:45:27

标签: unit-testing tdd mocking

我有一个同事为对象填​​充单元测试,这些对象用随机数据填充他们的字段。他的理由是它提供了更广泛的测试,因为它会测试很多不同的值,而普通测试只使用一个静态值。

我给了他许多不同的理由,主要是:

  • 随机值意味着测试不是真正可重复的(这也意味着如果测试可以随机失败,它可以在构建服务器上执行并打破构建)
  • 如果它是一个随机值并且测试失败,我们需要a)修复对象并且b)强迫我们每次都测试该值,所以我们知道它有效,但是因为它是随机的我们不知道是什么价值是

另一位同事补充说:

  • 如果我正在测试异常,则随机值将无法确保测试最终处于预期状态
  • 随机数据用于清除系统和负载测试,而不是用于单元测试

其他人可以添加其他原因我可以让他停止这样做吗?

(或者,这是一种可以接受的编写单元测试的方法,我和我的其他同事都错了吗?)

17 个答案:

答案 0 :(得分:69)

有妥协。你的同事实际上是在做某事,但我认为他做错了。我不确定完全随机的测试是非常有用的,但它当然不是无效的。

程序(或单元)规范是存在一些满足它的程序的假设。程序本身就是该假设的证据。单元测试应该是试图提供反证据来反驳该程序是否符合规范。

现在,您可以手动编写单元测试,但这确实是一项机械任务。它可以自动化。您所要做的就是编写规范,机器可以生成大量的单元测试,试图破坏您的代码。

我不知道你使用的是什么语言,但请看这里:

爪哇 http://functionaljava.org/

Scala(或Java) http://github.com/rickynils/scalacheck

的Haskell http://www.cs.chalmers.se/~rjmh/QuickCheck/

.NET: http://blogs.msdn.com/dsyme/archive/2008/08/09/fscheck-0-2.aspx

这些工具将您精心设计的规范作为输入,并使用自动生成的数据自动生成任意数量的单元测试。他们使用“缩小”策略(您可以调整)来找到最简单的测试用例来破坏您的代码并确保它能很好地覆盖边缘情况。

快乐测试!

答案 1 :(得分:36)

这种测试称为Monkey test。如果做得好,它可以从真正黑暗的角落抽出虫子。

解决您对可重复性的担忧:正确的方法是记录失败的测试条目,生成单元测试,探测特定错误的整个系列;并在单元测试中包括导致初始失败的一个特定输入(来自随机数据)。

答案 2 :(得分:22)

这里有一个中途的房子有一些用途,这是为你的PRNG播种常数。这允许您生成可重复的“随机”数据。

就我个人而言,我认为有些地方(常量)随机数据在测试中很有用 - 在你认为你已经完成所有仔细考虑过的角落后,使用PRNG的刺激有时可以找到其他东西。

答案 3 :(得分:12)

在书Beautiful Code中,有一章称为“美丽测试”,他在那里经历了Binary Search算法的测试策略。一段称为“随机测试行为”,其中他创建随机数组以彻底测试算法。您可以在Google图书上page 95在线阅读其中一些内容,但这本书值得一试。

所以基本上这只是表明生成随机数据进行测试是一个可行的选择。

答案 4 :(得分:10)

观察测试的人的一个优点是任意数据显然不重要。我看过太多涉及数十个数据的测试,很难分辨出那种方式需要什么,以及恰好是这样。例如。如果使用特定的邮政编码测试地址验证方法,并且所有其他数据都是随机的,那么您可以非常确定邮政编码是唯一重要的部分。

答案 5 :(得分:9)

如果您正在进行TDD,那么我认为随机数据是一种很好的方法。如果您的测试是用常量编写的,那么您只能保证您的代码适用于特定值。如果您的测试随机地使构建服务器失败,则可能存在测试编写方式的问题。

随机数据将有助于确保任何未来的重构都不会依赖于魔法常量。毕竟,如果您的测试是您的文档,那么常量的存在是否意味着它只需要对这些常量起作用?

我夸大了但是我更倾向于将随机数据注入我的测试,作为“此变量的值不应影响此测试结果”的标志。

我会说,如果你使用随机变量然后根据该变量分叉测试,那就是气味。

答案 6 :(得分:8)

  
      
  • 如果它是一个随机值并且测试失败,我们需要a)修复对象并且b)强迫我们每次都测试该值,所以我们知道它有效,但是因为它是随机的我们不知道是什么价值是
  •   

如果您的测试用例没有准确记录测试内容,可能需要重新编写测试用例。我总是希望有一些我可以参考测试用例的日志,以便我确切地知道是什么原因导致它失败,无论是使用静态数据还是随机数据。

答案 7 :(得分:7)

我赞成随机测试,我写它们。但是,它们是否适用于特定的构建环境以及它们应包含在哪些测试套件中是一个更细微的问题。

在本地运行(例如,在你的开发盒上过夜),随机测试发现了明显和模糊的错误。这些模糊不清的东西足够神秘,我认为随机测试真的是唯一能够将它们清除掉的现实测试。作为测试,我通过随机测试发现了一个难以发现的bug,并且有六个破解开发人员审查了它发生的功能(大约十几行代码)。没有人能够发现它。

许多反对随机数据的论据都是"测试不可重复的#34;。然而,一个写得很好的随机测试将捕获用于启动随机化种子的种子并在失败时输出它。除了允许您手动重复测试之外,这还允许您轻松创建新测试,通过对该测试的种子进行硬编码来测试特定问题。当然,手动编写覆盖该案例的显式测试可能更好,但懒惰有其优点,这甚至允许您从失败的种子中自动生成新的测试用例。

然而,你提出的我无法辩论的一点是它打破了构建系统。大多数构建和持续集成测试都希望测试每次都能做同样的事情。因此,随机失败的测试会造成混乱,随机失败并指责无害的变化。

然后,解决方案仍然是将随机化测试作为构建和CI测试的一部分运行,但使用固定种子运行它,进行固定次数的迭代。因此测试总是做同样的事情,但仍然探索了一堆输入空间(如果你运行多次迭代)。

在本地,例如,在更改相关类时,您可以自由运行它以进行更多迭代或使用其他种子。如果随机测试变得越来越流行,您甚至可以想象一组特定的测试,这些测试是随机的,可以用不同的种子运行(因此随着时间的推移而增加覆盖率),并且失败并不代表与确定性CI系统相同的事情(即,运行不会以1:1的方式与代码更改相关联,因此当事情失败时,您不会指出特定的更改。)

随机测试有很多要说的,特别是写好的,所以不要过快地解雇它们!

答案 8 :(得分:7)

你的同事正在做fuzz-testing,虽然他不知道。它们在服务器系统中特别有价值。

答案 9 :(得分:4)

你可以生成一些随机数据(我的意思是一次,不是每次测试运行一次),然后在之后的所有测试中使用它吗?

我绝对可以看到创建随机数据以测试那些你没有想过的案例的价值,但是你是对的,有可能随机通过或失败的单元测试是一件坏事。

答案 10 :(得分:4)

你应该问自己测试的目标是什么 单元测试是关于验证逻辑,代码流和对象交互。使用随机值尝试实现不同的目标,从而减少测试焦点和简单性。出于可读性原因(生成UUID,ID,密钥等),这是可以接受的 特别是对于单元测试,即使这种方法成功发现问题,我也记不起来了。但我已经看到许多确定性问题(在测试中)试图用随机值聪明,主要是随机日期 模糊测试是集成测试端到端测试的有效方法。

答案 11 :(得分:1)

如果您在测试中使用随机输入,则需要记录输入,以便查看值。这样,如果遇到一些边缘情况,可以编写测试以重现它。我听说过人们没有使用随机输入的相同原因,但是一旦你深入了解了特定测试运行所使用的实际值,那么它就不是一个问题了。

“任意”数据的概念作为表示重要的内容的方式也非常有用。我们进行了一些验收测试,其中存在大量与手头测试无关的噪声数据。

答案 12 :(得分:0)

我可以设想测试数据问题的三种解决方案:

  • 使用固定数据进行测试
  • 使用随机数据进行测试
  • 生成随机数据一次,然后将其用作固定数据

我建议以上所有。也就是说,使用你的大脑编写一些边缘情况的可重复单元测试,以及一些你只生成一次的随机数据。然后编写一组随机运行的测试以及

永远不应期望随机测试能够捕捉到可重复测试遗漏的内容。您应该通过可重复的测试来覆盖所有内容,并将随机测试视为奖励。如果他们找到了某些东西,它应该是你无法合理预测的东西;一个真正古怪的人。

答案 13 :(得分:0)

我们今天遇到了这个问题。我想要伪随机(所以它看起来就像大小一样压缩音频数据)。我认为我还想要确定性。 rand()在OSX上与在Linux上不同。除非我重新播种,否则它可能会随时改变。因此我们将其更改为确定性但仍然伪随机:测试是可重复的,与使用固定数据一样多(但更方便地编写)。

这是 NOT 通过代码路径的一些随机暴力测试。这就是区别:仍然是确定性的,仍然是可重复的,仍然使用看似真实输入的数据来对复杂逻辑中的边缘情况进行一系列有趣的检查。还是单元测试。

这仍然符合条件是随机的吗?我们来谈谈啤酒。 : - )

答案 14 :(得分:0)

根据您的对象/应用程序,随机数据将在负载测试中占有一席之地。我认为更重要的是使用明确测试数据边界条件的数据。

答案 15 :(得分:0)

我认为这里的问题是单元测试的目的不是捕捉错误。目的是能够在不破坏代码的情况下更改代码,所以你怎么知道你破坏了 当您的随机单元测试在您的管道中是绿色时,您的代码只是因为它们没有触及正确的路径? 这样做对我来说是疯狂的。另一种情况可能是将它们作为集成测试或 e2e 运行而不是作为构建的一部分,并且仅用于某些特定的事情,因为在某些情况下,您将需要断言中的代码镜像来进行测试。 拥有与真实代码一样复杂的测试套件就像根本没有测试一样,因为谁 那么要测试你的套件吗? :p

答案 16 :(得分:-1)

当你的家伙未能看到他是否已经修好它时,你怎么能再次进行测试呢?即他失去了测试的可重复性。

虽然我认为在测试中投入大量随机数据可能有一些价值,正如其他回复中所提到的,它在负载测试的标题下比其他任何内容都要多。这几乎是一种“希望测试”的做法。我认为,实际上,你的家伙根本就没有想到他想要测试什么,并且希望通过随机性来弥补这种缺乏思想最终会陷入一些神秘的错误。

所以我和他一起使用的论点是他很懒。或者,换句话说,如果他没有花时间去理解他试图测试的东西,那么可能表明他并不真正理解他正在编写的代码。