单元测试中的随机数据?


136

我有一个同事,他为用随机数据填充其字段的对象编写单元测试。他的原因是它提供了更广泛的测试范围,因为它将测试许多不同的值,而普通测试仅使用单个静态值。

我为此提出了许多不同的理由,主要的理由是:

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

另一位同事补充说:

  • 如果我正在测试异常,则随机值将无法确保测试以预期状态结束
  • 随机数据用于冲洗系统和负载测试,而不用于单元测试

还有谁能补充我可以给他的其他理由,让他停止这样做?

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


32
“随机值意味着测试不是真正可重复的”不是正确的,因为tets将使用伪随机数。提供相同的初始种子,获得相同的“随机”测试序列。
Raedwald

11
轶事:我曾经写过一个CSV导出类,并且在将控制字符放在单元格末尾的随机测试中发现了一个错误。没有随机测试,我将永远不会想到将其添加为测试用例。它总是失败吗?不,这是一个完美的测试吗?否。它有助于我捕获并修复错误吗?是。
Tyzoid

1
测试还可以用作文档,以解释代码何时期望作为输入以及期望什么作为输出。与生成随机数据的代码相比,使用明确的任意数据进行测试可以更简单,更具解释性。
夹板

如果您的单元测试由于随机生成的值而失败,并且该值不是assert的一部分,请调试您的单元测试,祝您好运。
eriksmith200

Answers:


72

有妥协。您的同事实际上是在做某事,但我认为他做错了。我不确定完全随机测试是否很有用,但肯定不是无效的。

程序(或单元)规范是一种假设,即存在一些符合要求的程序。然后,程序本身就是该假设的证据。应该进行哪种单元测试是为了提供反证,以反驳该程序是否按照规范运行。

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

我不知道您使用的是哪种语言,但请参见此处:

Java 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
为此+1。ScalaCheck在以可重复的方式生成最小化的随机测试数据方面做得非常出色。
Daniel Spiewak

17
这不是随机的。这是任意的。很大的不同:)
Apocalisp

reductiotest.org现在似乎不再存在,Google在其他任何地方都没有指出我。知道现在在哪里吗?
Raedwald

现在,它是Functional Java库的一部分。链接已编辑。但是我只会使用Scalacheck来测试Java代码。
Apocalisp

ScalaCheck已迁移到GitHub。更新了答案链接。它对Java也有用,而不仅仅是Scala。(老链接是code.google.com/p/scalacheck
RobertB

38

这种测试称为Monkey测试。如果操作正确,它可以从真正黑暗的角落冒出错误。

解决您对可重复性的担忧:解决此问题的正确方法是记录失败的测试条目,生成一个单元测试,以检测特定缺陷的整个家族;并在单元测试中包括一个导致初始故障的特定输入(来自随机数据)。


26

这里有一个半途而废的房子,有一定用处,可以用一个常数为PRNG播种。这使您可以生成可重复的“随机”数据。

我个人确实认为在某些地方,(恒定)随机数据可用于测试-在您认为自己已经完成了所有经过深思熟虑的角落后,使用PRNG的刺激有时会发现其他东西。


4
在具有大量锁定和线程的系统上,我已经看到了这项工作。每次运行都会将“随机”种子写入文件,然后,如果运行失败,我们可以找出代码采用的路径,并为我们错过的情况编写一个手写的单元测试。
伊恩·林格罗斯

PRNG代表什么?
systemovich

伪随机数生成器
院长将于

16

在《美丽代码》一书中,有一章称为“美丽测试”,他在其中介绍了二进制搜索算法的测试策略。一段称为“测试的随机行为”,其中他创建了随机数组以彻底测试算法。您可以在第95页的 Google图书上在线阅读其中的一些内容,但这是一本值得拥有的好书。

因此,基本上,这仅表明生成随机数据进行测试是可行的选择。


16

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

在本地运行(例如,在开发人员的机器上过夜),随机测试发现错误既明显又模糊。晦涩难懂的测试很神秘,以至于我认为随机测试确实是唯一可以将其清除的现实方法。作为测试,我采取了一个通过随机测试发现的难以发现的错误,并让六个破解程序的开发人员检查了该功能所在的地方(大约十二行代码)。没有人能够检测到它。

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

但是,我不能争辩的一点是,它破坏了构建系统。大多数构建和持续集成测试都希望测试每次都执行相同的操作。因此,随机失败的测试会造成混乱,随机失败,并将手指指向无害的更改。

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

在本地,例如,当更改相关的类时,您可以自由地运行它进行更多迭代或与其他种子一起运行。如果随机化测试变得越来越流行,您甚至可以想象一套特定的测试是随机的,可以用不同的种子运行(因此随着时间的推移覆盖面会不断增加),并且失败不会意味着同一件事作为确定性CI系统(例如,运行不会与代码更改1:1关联,因此当事情失败时,您不必指责特定的更改)。

对于随机测试,尤其是写得很好的测试,有很多要说的,因此不要太快就将其驳回!


14

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

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

我有点夸张,但是我更喜欢将随机数据注入测试中,以表明“此变量的值不应影响该测试的结果”。

我要说的是,如果您使用随机变量,然后基于该变量进行测试,那是一种气味。


10

查看测试的人的一个优势是,任意数据显然并不重要。我见过太多涉及数十个数据的测试,可能很难分辨出需要哪种方式以及刚发生的那种方式。例如,如果使用特定的邮政编码对地址验证方法进行了测试,并且所有其他数据都是随机的,那么您可以确定邮政编码是唯一重要的部分。


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

如果您的测试用例无法准确记录其测试内容,则可能需要重新编码测试用例。我一直想拥有可供测试用例参考的日志,以便我确切地知道是什么原因导致了使用静态或随机数据的失败。


9

您的同事正在进行模糊测试,尽管他不知道。它们在服务器系统中特别有价值。


2
但这与单元测试根本不同吗?并在其他时间完成?
endlith 2014年

1
@endolith没有物理定律强迫您在特定时间运行特定测试
253751,2015年

1
@immibis但是有充分的理由在特定时间进行特定测试。用户每次单击“确定”按钮时,都无需运行电池测试。
endlith 2015年

5

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

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


5

您应该问自己考试的目的是什么。
单元测试是关于验证逻辑,代码流和对象交互的。使用随机值会尝试实现不同的目标,从而降低测试重点和​​简化性。出于可读性原因(生成UUID,ID,密钥等),它是可接受的。
特别是对于单元测试,即使此方法成功发现问题,我也无法回忆。但是我已经看到许多确定性问题(在测试中)试图对随机值(主要是随机日期)变得更聪明。
模糊测试是用于集成测试端到端测试的有效方法。


我还要补充说,在可能的情况下,使用随机输入进行模糊测试不能很好地替代覆盖指导的模糊测试。
gobenji

1

如果您使用随机输入进行测试,则需要记录输入内容,以便查看值是什么。这样,如果遇到一些极端情况,您可以编写测试以重现它。我听到人们不使用随机输入的相同原因,但是一旦您了解了用于特定测试运行的实际值,那么就不再是问题。

“任意”数据的概念也为预示的东西,是一种方式非常有用的并不重要。我们想到了一些验收测试,其中存在大量与手头测试无关的噪声数据。


0

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


0

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

不是通过某种随机的蛮力通过代码路径进行测试。区别在于:仍然是确定性的,仍然可重复的,仍然使用看起来像真实输入的数据对复杂逻辑中的边缘情况进行一组有趣的检查。仍然是单元测试。

那还算是随机的吗?让我们来谈谈啤酒。:-)


0

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

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

我建议做以上所有事情。就是说,编写可重复的单元测试,包括用大脑计算出的一些边缘情况以及只生成一次的一些随机数据。然后编写一组您还要运行的随机测试。

不应期望随机测试会发现您的可重复测试遗漏的东西。您应该努力以可重复的测试覆盖所有内容,并认为随机化测试是一个加分项。如果他们发现了某些东西,那应该是您无法合理预测的。一个真正的怪胎。


-2

如果您的人看不到他是否已解决问题,如何再次进行测试?即他失去了测试的可重复性。

尽管我认为在测试中释放随机数据负载可能会有些价值,但正如其他答复中所提到的那样,它比负载测试更重要。这几乎是“希望测试”的做法。我认为,实际上,您的家伙根本就不在考虑自己要测试的内容,而希望通过随机性来弥补这种思想上的不足,最终会陷入一些神秘的错误。

所以我要和他一起使用的论据是他很懒。或者换一种说法,如果他没有花时间去理解他要测试的内容,那可能表明他并不真正理解他正在编写的代码。


3
可以记录随机数据或随机种子,以便可以重现测试。
cbp
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.