生成随机输出的代码如何进行单元测试最佳?

26

具体来说,我有一个方法可以从列表中选择 n 个项目,并使其中 a% 满足一个条件,b% 满足另一个条件等等。一个简化的例子是选择 5 个项目,其中 50% 具有给定属性 'true' 的值,而 50% 具有值 'false';在这种情况下,该方法会有 50% 的概率返回 2 个 true / 3 个 false,另外 50% 的概率返回 3 个 true / 2 个 false。

从统计学上讲,这意味着在 100 次运行中,我应该得到大约 250 个 true 和 250 个 false,但由于随机性,240/260 是完全可能的。

最好的单元测试方法是什么?我假设即使在技术上 300/200 是可能的,如果发生这种情况,它也应该不通过测试。是否存在一般可接受的容差范围,如果有,如何确定这个容差范围?

编辑:在我正在处理的代码中,我不能使用伪随机数生成器或强制平衡随机数分布的机制,因为选出的列表是在不同的机器上生成的。我需要能够证明,随着时间的推移,每个条件匹配的项目的平均数量将趋向于所需的百分比。


在这个例子中,你想要至少/恰好50%还是大约50%?更具体地说,这个测试中的随机性是什么? - Gishu
1
我怀疑你实际上并没有编写单元测试。如果你正在测试运行在不同机器上的服务器,那么你实际上处于系统测试阶段。我只能告诉你:如果你为运行在这些服务器上的类编写了单元测试,就不再需要那么多(详细的)系统测试了。 - Stefan Steinegger
不,重点是要确认算法50%的时间选择2/3,另外50%选择3/2(在这个简单的例子中),无论它在哪里使用。不是一个系统将会生成它; 在一台机器上运行1000次被认为是适当的测试,以证明如果在100台机器上运行10次,它会平均分配,这就是我计划进行单元测试的原因。 - Flynn1179
可能是单元测试 - 如何测试返回随机输出的函数?的重复问题。 - Raedwald
9个回答

21

在单元测试中,随机和统计不受青睐。单元测试应该始终返回相同的结果。始终如此,而不是大多数情况下。

你可以尝试移除你正在测试的逻辑中的随机生成器。然后你可以模拟随机生成器并返回预定义的值。


其他想法:

你可以考虑改变实现方式使其更易于测试。尽可能少地获得随机值。例如,你只需要获取一个随机值来确定偏离平均分布的程度。这将很容易进行测试。如果随机值为零,你应该得到你期望的精确分布。如果值为1.0,你可能会以某个定义的因素(例如10%)错过平均值。你也可以实现一些高斯分布等。我知道这不是这里的主题,但如果你可以自由实现它,要考虑可测试性。


你不认为“值必须小于x”是一个有效的单元测试吗?无论如何,正如我在编辑中澄清的那样,将生成这些列表的是不同的机器,这使得任何形式的强制分配都是不可能的 :( - Flynn1179
随机性是完全可以接受的。除非这种随机性会测试不同的代码路径,否则会使得测试跟踪非常困难,并且失去使用它们的目的。 - Stefan Valianu
是的,我想我会选择模拟随机数生成器来提供预定值;我的实际问题比我的示例复杂得多,但我应该能够从给定的“随机”数字列表中推导出期望的结果以进行测试。 - Flynn1179

4
根据您提供的统计信息,确定一个范围而不是具体数值作为结果。

3
只要测试中存在任何随机因素,就可能会出现假阳性或假阴性。 - Stefan Steinegger

4
许多概率算法在科学计算中使用伪随机数生成器,而不是真正的随机数生成器。尽管它们并非真正的随机数,但精心选择的伪随机数生成器可以完成工作。
伪随机数生成器的一个优点是,它们产生的随机数序列是完全可重复的。由于算法是确定性的,相同的种子总是会生成相同的序列。这通常是选择它们的决定性因素之一,因为实验需要可重复性,结果可再现性。
这个概念也适用于测试。组件可以被设计成可以插入任何随机数源。对于测试,您可以使用始终具有相同种子的生成器。结果将是可重复的,适合测试。
请注意,如果确实需要一个真正的随机数,只要组件具有可插拔的随机数源,仍然可以通过这种方式进行测试。您可以将相同的序列(如果需要可能是真正的随机数)重新插入到相同的组件中进行测试。

4
我觉得你至少想要测试三个不同的东西:
1. 使用随机源生成输出的过程的正确性 2. 随机源的分布是否符合预期 3. 输出的分布是否符合预期
1 应该是确定性的,您可以通过提供一组已知的“随机”值和输入并检查其是否产生已知的正确输出来进行单元测试。如果您将随机源作为参数传递而不是嵌入在代码中,则这将是最简单的。
2 和 3 无法被绝对测试。您可以测试到某个选择的置信水平,但必须准备好在某些情况下测试失败。可能您真正要注意的是,测试 3 失败的次数比测试 2 多得多,因为这表明您的算法是错误的。
应用的测试取决于预期的分布。对于 2,您最有可能希望随机源均匀分布。有各种测试方法,具体取决于您想要多么深入,例如请参见此页面上伪随机数生成器的测试
3的预期分布将在很大程度上取决于您正在生产的内容。问题中简单的50-50情况与测试公平硬币完全相同,但显然其他情况会更加复杂。如果您能确定应该是什么分布,那么对其进行卡方检验可能会有所帮助。

3
这取决于你对测试套件的使用。如果你采用测试驱动开发和积极重构的方式,每隔几秒钟运行一次测试,则非常重要的是它不会出现假警报,因为这会导致严重的混乱和降低生产力,所以你应该选择一个对于良好实现来说几乎不可能达到的阈值。如果你每晚只运行一次测试并有时间调查失败,那么你就可以变得更加严格。
无论什么情况下,都不应该部署会导致频繁未经调查的失败的东西 - 这违背了拥有测试套件的整个目的,并且大大降低了它对团队的价值。

2
您应该在“单个”单元测试中测试结果的分布,即任何单个运行的结果都应尽可能接近所需的分布。对于您的示例,2个true / 3个false是可以的,4个true / 1个false不是一个合格的结果。

此外,您可以编写测试,执行该方法100次,并检查分布的平均值是否“足够接近”所需比率。这是一个边界情况 - 运行更大的批处理可能需要相当长的时间,因此您可能希望将这些测试与您的“常规”单元测试分开运行。此外,正如Stefan Steinegger指出的那样,如果您将“足够接近”的定义更严格或者将阈值定义得太宽泛,这样的测试偶尔会失败,或者开始变得毫无意义。所以这是一个棘手的情况...


1

我认为如果我遇到同样的问题,我可能会构建一个置信区间来检测异常值,如果你有一些关于平均值/标准差等统计数据。因此,在您的情况下,如果平均预期值为250,则使用正态分布创建围绕平均值的95%置信区间。如果结果在该区间之外,则测试失败。

查看更多信息


0
为什么不重构随机数生成代码,让单元测试框架和源代码都使用它呢?你试图测试的是算法而不是随机序列,对吧?

0

首先,您需要了解随机数生成过程应产生哪种分布。在您的情况下,您正在生成一个结果,该结果的概率为0或1,概率为0.5。这描述了一个p=0.5的二项分布

给定样本大小n,您可以构建(如早期的帖子所建议的)围绕平均值的置信区间。您还可以对在n = 500时获得240或更少的任一结果的概率做出各种陈述。

对于大于20的N值,只要p不是非常大或非常小,您可以使用正态分布假设。维基百科的文章有更多相关信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接