对生成随机输出的代码进行单元测试的最佳方法是啥?
Posted
技术标签:
【中文标题】对生成随机输出的代码进行单元测试的最佳方法是啥?【英文标题】:What's the best way to unit test code that generates random output?对生成随机输出的代码进行单元测试的最佳方法是什么? 【发布时间】:2011-03-05 08:31:15 【问题描述】:具体来说,我有一个方法从列表中挑选 n 个项目,其中 a% 满足一个条件,b% 满足第二个条件,依此类推。一个简化的示例是选择 5 个项目,其中 50% 的给定属性值为“true”,50% 的值为“false”; 50% 的时间该方法将返回 2 true/3 false,而另外 50% 的时间返回 3 true/2 false。
从统计上讲,这意味着超过 100 次运行,我应该得到大约 250 真/250 假,但由于随机性,240/260 是完全可能的。
对此进行单元测试的最佳方法是什么?我假设即使在技术上 300/200 是可能的,但如果发生这种情况,它可能应该无法通过测试。对于这种情况,是否存在普遍接受的容忍度,如果有,您如何确定是什么?
编辑:在我正在处理的代码中,我没有使用伪随机数生成器的奢侈,或者强制它随着时间的推移而平衡的机制,因为挑选出来的列表是在不同的机器上生成。我需要能够证明,随着时间的推移,符合每个标准的平均项目数将趋于所需的百分比。
【问题讨论】:
在示例中,您想要至少/正好 50% 还是大约 50%?更具体地说,这个测试的随机性是什么? 我怀疑您实际上并没有编写单元测试。如果您正在测试在不同机器上运行的服务器,那么您实际上是在进行系统测试。我只能告诉你:如果你对这些服务器上运行的类进行了单元测试,你就不再需要这么多(详细的)系统测试了。 不,关键是要确认算法确实选择了 2/3 50% 的时间,以及其他 50% 的 3/2(在这个简单的示例中),无论它在哪里使用。这不是一个系统会产生这个。在一台机器上运行 1000 次被认为是一个足够的测试,以证明如果它在全球 100 台机器上运行 10 次,它会得到适当的平均,因此我计划对它进行单元测试。 Unit testing - how do I test a function that returns random output?的可能重复 【参考方案1】:随机和统计在单元测试中不受欢迎。单元测试应该总是返回相同的结果。总是。不是大部分。
您可以做的是尝试删除您正在测试的逻辑的随机生成器。然后你可以模拟随机生成器并返回预定义的值。
其他想法:
您可以考虑更改实现以使其更具可测试性。尝试获得尽可能少的随机值。例如,您只能获得一个随机值来确定与平均分布的偏差。这很容易测试。如果随机值为零,您应该得到您期望的平均分布。例如,如果该值是 1.0,则您会因某个定义的因素而错过平均值,例如 10%。您还可以实现一些高斯分布等。我知道这不是这里的主题,但如果您可以随意实现它,请考虑可测试性。
【讨论】:
你不会认为'value must be 随机性是完全允许的。除非随机性是在测试不同的代码路径,否则这会使跟踪测试变得很糟糕,并且违背了使用它们的目的。 是的,我想我将模拟 RNG 以提供预先确定的值;我的实际问题比我的示例复杂得多,但我应该能够从给定的“随机”数字列表中得出预期结果以进行测试。【参考方案2】:根据您拥有的统计信息,确定一个范围而不是一个特定的单个值作为结果。
【讨论】:
只要你在测试中有任何随机,它仍然可能是假阳性或假阴性。【参考方案3】:许多概率算法,例如科学计算使用 pseudo-random number generators,而不是 true 随机数生成器。即使它们不是真正随机的,一个精心挑选的伪-随机数生成器也能很好地完成这项工作。
伪-随机数生成器的一个优点是它们产生的随机数序列是完全可重现的。由于算法是确定性的,相同的 seed 将始终生成相同的序列。这通常是首先选择它们的决定因素,因为实验需要可重复,结果可重复。
这个概念也适用于测试。可以设计组件,以便您可以插入任何随机数源。对于测试,您可以使用持续播种的生成器。结果将是可重复的,适合测试。
请注意,如果实际上需要 true 随机数,您可以仍然以这种方式对其进行测试,只要组件具有可插入的随机数源即可。您可以将相同的序列(如果需要,可能是真正随机的)重新插入到相同的组件中进行测试。
【讨论】:
【参考方案4】:在我看来,您至少要在这里测试三个不同的东西:
-
使用随机源生成输出的过程的正确性
随机源的分布是你所期望的
输出的分布是您所期望的
1 应该是确定性的,您可以通过提供一组选定的已知“随机”值和输入并检查它是否产生已知的正确输出来对其进行单元测试。如果您构造代码以便将随机源作为参数传递而不是嵌入到代码中,这将是最简单的。
2 和 3 不能绝对测试。您可以测试到某个选定的置信水平,但您必须为此类测试在某些情况下失败做好准备。可能您真正要注意的是测试 3 比测试 2 失败的频率更高,因为这表明您的算法是错误的。
要应用的测试取决于预期的分布。对于 2,您很可能期望随机源是均匀分布的。对此有各种测试,具体取决于您希望参与的程度,例如参见Tests for pseudo-random number generators on this page。
3 的预期分布在很大程度上取决于您所生产的产品。问题中简单的 50-50 的情况完全等价于testing for a fair coin,但显然其他情况会更复杂。如果你能弄清楚分布应该是什么,反对它的chi-square test 可能会有所帮助。
【讨论】:
【参考方案5】:这取决于您对测试套件的使用。如果你因为拥抱测试驱动开发和积极重构而每隔几秒运行一次,那么它不会虚假失败是非常重要的,因为这会导致重大中断并降低生产力,所以你应该选择一个几乎不可能的阈值达到一个良好的执行。如果您每晚运行一次测试并有时间调查失败,您可以更加严格。
在任何情况下,您都不应部署会导致频繁出现未经调查的故障的东西 - 这违背了拥有测试套件的全部目的,并大大降低了它对团队的价值。
【讨论】:
【参考方案6】:您应该在“单一”单元测试中测试结果的分布,即在任何单独的运行中结果都尽可能接近所需的分布。对于您的示例,2 true / 3 false 可以,结果 4 true / 1 false 不可以。
您还可以编写执行该方法的测试,例如100 次并检查分布的平均值是否“足够接近”所需的速率。这是一个临界案例——运行更大的批次可能需要大量时间,因此您可能希望将这些测试与“常规”单元测试分开运行。此外,正如 Stefan Steinegger 指出的那样,如果您将“足够接近”定义得更严格,或者如果您将阈值定义得太松散,那么这样的测试就会时不时地失败。所以这是一个棘手的案例......
【讨论】:
【参考方案7】:我想如果我遇到同样的问题,如果你有一些关于平均值/标准差等的统计数据,我可能会构建一个置信区间来检测异常。因此,在您的情况下,如果平均预期值为 250,则使用正态分布围绕平均值创建 95% 的置信区间。如果结果超出该区间,则您的测试失败。
见more
【讨论】:
【参考方案8】:为什么不重构随机数生成代码,让单元测试框架和源代码都使用它呢?您正在尝试测试您的算法而不是随机序列,对吗?
【讨论】:
【参考方案9】:首先,您必须知道随机数生成过程应该产生什么样的分布。在您的情况下,您生成的结果为 0 或 1,概率为 -0.5。这描述了 p=0.5 的binomial distribution。
给定 n 的样本量,您可以(如之前的发帖人所建议的)围绕均值构建置信区间。例如,当 n=500 时,您还可以就获得任一结果的概率做出各种陈述,例如,240 或更少。
只要 p 不是很大或很小,您就可以对大于 20 的 N 值使用正态分布假设。***的帖子对此有更多的了解。
【讨论】:
以上是关于对生成随机输出的代码进行单元测试的最佳方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章
在 Laravel 中对控制器进行单元测试而不测试路由的最佳方法是啥
对 ASP.NET 2.0 网页进行单元测试的最佳方法是啥? [关闭]