164

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

我给了他很多不同的理由来反对这一点,主要是:

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

另一位同事补充道:

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

还有其他人可以添加我可以给他的其他理由来让他停止这样做吗?

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

4

19 回答 19

81

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

程序(或单元)规范是一种假设,即存在一些满足它的程序。程序本身就是该假设的证据。单元测试应该是试图提供反证据来反驳程序按照规范工作。

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

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

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

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

祝测试愉快!

于 2008-08-28T15:46:31.963 回答
47

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

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

于 2008-09-18T01:21:08.017 回答
28

这里有一个中途之家,它有一些用处,就是给你的 PRNG 播种一个常数。这使您可以生成可重复的“随机”数据。

就我个人而言,我确实认为有些地方(恒定)随机数据在测试中很有用——在你认为你已经完成了所有经过深思熟虑的角落之后,使用来自 PRNG 的刺激有时可以找到其他东西。

于 2008-08-28T15:09:04.613 回答
21

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

在本地运行(例如,在您的开发盒上过夜)随机测试发现了明显和模糊的错误。那些晦涩难懂的东西太神秘了,以至于我认为随机测试确实是唯一能将它们淘汰的现实测试。作为测试,我通过随机测试发现了一个难以发现的错误,并让六名破解开发人员审查了它发生的功能(大约十几行代码)。没有人能够检测到它。

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

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

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

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

随机测试有很多话要说,尤其是写得好的测试,所以不要太快放弃它们!

于 2016-06-16T00:51:25.373 回答
18

Beautiful Code一书中,有一章叫做“Beautiful Tests”,他在其中介绍了二分搜索算法的测试策略。其中一段被称为“随机测试行为”,他在其中创建了随机数组来彻底测试算法。您可以在 Google 图书第 95 页在线阅读其中的一些内容,但这是一本值得拥有的好书。

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

于 2008-08-28T15:12:04.357 回答
18

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

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

我夸大了,但是我更喜欢在我的测试中注入随机数据作为“这个变量的值不应该影响这个测试的结果”的标志。

我会说,如果您使用随机变量然后基于该变量分叉您的测试,那么这是一种气味。

于 2013-06-14T14:39:52.183 回答
12

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

于 2009-03-13T05:46:32.797 回答
12

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

于 2010-12-20T17:10:01.360 回答
9
  • 如果它是一个随机值并且测试失败,我们需要 a) 修复对象 b) 每次都强制自己测试该值,所以我们知道它有效,但由于它是随机的,我们不知道该值是什么

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

于 2008-08-28T14:57:55.217 回答
4

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

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

于 2008-08-28T14:55:05.457 回答
4

你应该问问自己你的测试目标是什么。
单元测试是关于验证逻辑、代码流和对象交互的。使用随机值试图达到不同的目标,从而降低了测试的重点和简单性。出于可读性原因(生成 UUID、id、键等)是可以接受的。
特别是对于单元测试,即使这种方法成功地发现了问题,我也不记得了。但是我已经看到许多确定性问题(在测试中)试图巧妙地处理随机值,主要是随机日期。
模糊测试是集成测试端到端测试的有效方法。

于 2014-10-05T22:11:53.620 回答
3

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

“任意”数据的概念作为表示不重要事物的一种方式也非常有用。我们想到了一些验收测试,其中有很多与手头的测试无关的噪声数据。

于 2008-10-09T03:26:04.803 回答
2

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

于 2021-02-26T00:54:03.900 回答
0

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

于 2008-08-28T14:51:06.807 回答
0

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

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

那仍然是随机的吗?让我们谈谈啤酒。:-)

于 2008-09-18T01:15:41.757 回答
0

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

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

我建议做以上所有事情。也就是说,编写可重复的单元测试,其中包含使用您的大脑解决的一些边缘案例,以及一些您只生成一次的随机数据。然后编写一组您也运行的随机测试

永远不要期望随机测试能够捕捉到您的可重复测试遗漏的东西。您应该旨在通过可重复的测试涵盖所有内容,并将随机测试视为奖励。如果他们发现了什么,那应该是你无法合理预测的东西;一个真正的怪人。

于 2011-10-26T16:32:11.473 回答
0

单元测试可以确保响应特定输入的正确行为,特别是应涵盖所有代码路径/逻辑。不需要使用随机数据来实现这一点。如果您的单元测试没有 100% 的代码覆盖率,那么后门的模糊测试将无法实现这一目标,甚至可能意味着您偶尔无法实现所需的代码覆盖率。它可能(请原谅双关语)给您一种“模糊”的感觉,即您正在使用更多的代码路径,但这背后可能没有太多的科学依据。人们经常在第一次运行单元测试时检查代码覆盖率,然后忘记它(除非由 CI 强制执行),所以你真的想通过使用随机输入数据来检查每次运行的覆盖率吗?这只是另一件可能被忽视的事情。

此外,程序员倾向于走简单的道路,他们会犯错误。他们在单元测试中犯的错误与在被测代码中犯的错误一样多。有人很容易引入随机数据,然后在一次运行中将断言调整为输出顺序。承认吧,我们都做过。当数据更改时,顺序可能会更改并且断言失败,因此部分执行失败。这部分不必是 1/2 我已经看到这会导致 10% 的时间失败。追踪此类问题需要很长时间,如果您的 CI 没有记录足够多的运行数据,那么情况可能会更糟。

虽然有一种说法是“不要犯这些错误”,但在典型的商业编程设置中,会有各种能力,有时相对初级的人会为其他初级人员审查代码。您可以在调试一个非确定性测试并修复它所需的时间内编写数十个测试,因此请确保您没有任何测试。不要使用随机数据。

于 2022-01-20T13:15:20.610 回答
0

根据我的经验,单元测试和随机测试应该分开。单元测试用于确定某些情况的正确性,而不仅仅是为了捕捉晦涩难懂的错误。综上所述,随机测试很有用,应该与单元测试分开进行,但它应该测试一系列随机值。我不禁认为每次运行测试 1 个随机值是不够的,既不是足够的随机测试,也不是真正有用的单元测试。

另一方面是验证测试结果。如果您有随机输入,则必须在测试中为其计算预期输出。这将在某种程度上复制测试的逻辑,使测试只是测试代码本身的镜像。这将无法充分测试代码,因为测试可能包含与原始代码相同的错误。

于 2022-01-25T09:29:20.657 回答
-1

当测试失败时,您的人如何再次运行测试,看看他是否已修复它?即他失去了测试的可重复性。

虽然我认为在测试中抛出大量随机数据可能有一些价值,但正如其他回复中提到的那样,它更多地属于负载测试的标题。这几乎是一种“希望测试”的做法。我认为,实际上,你的人根本没有考虑他要测试的内容,并通过希望随机性最终会捕获一些神秘的错误来弥补这种缺乏思考。

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

于 2008-08-28T22:02:54.743 回答