关于tdd:单元测试中的随机数据?

关于tdd:单元测试中的随机数据?

Random data in Unit Tests?

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

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

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

另一位同事补充说:

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

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

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


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

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

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

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

爪哇
http://functionaljava.org/

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

哈斯克尔
http://www.cs.chalmers.se/~rjmh/QuickCheck/

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

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

测试愉快!


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

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


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

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


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

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


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

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

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

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


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


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

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

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

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

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

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

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


  • if it's a random value and the test fails, we need to a) fix the object and b) force ourselves to test for that value every time, so we know it works, but since it's random we don't know what the value was

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


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


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


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

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


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

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


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


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

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

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


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

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

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

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


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

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

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


推荐阅读