我应该如何测试随机性?


127

考虑一种随机调整数组元素的方法。您将如何编写一个简单而健壮的单元测试以确保其正常工作?

我提出了两个想法,两个都有明显的缺陷:

  • 随机排列数组,然后确保其顺序与以前不同。这听起来不错,但是如果随机播放以相同顺序随机播放,则失败。(不可能,但可能。)
  • 用恒定的种子对数组进行混洗,并根据预定的输出进行检查。这依赖于随机函数始终在给定相同种子的情况下始终返回相同的值。但是,有时这是一个无效的假设

考虑第二个函数,该函数模拟掷骰子并返回随机数。您将如何测试此功能?您将如何测试该功能...

  • 永远不会返回给定范围之外的数字?
  • 返回有效分布中的数字?(一个骰子统一,大量骰子正常。)

我正在寻找答案,以提供不仅可以测试这些示例,而且可以测试常规代码的随机元素。这里的单元测试甚至是正确的解决方案吗?如果没有,那是什么样的测试?


只是为了让大家放心,我没有编写自己的随机数生成器。


35
紧的联结器显示其头部。传递生成随机数的对象。然后,在测试过程中,您可以传递一个对象,该对象生成一组指定的数字,您知道这些数字后,洗牌后的面板是什么样的。您可以分别测试随机数生成器的随机性。
马丁·约克

1
我会强烈考虑将现有的库例程用于洗牌(java Collections.shuffle()或类似的)。在developer.com/tech/article.php/616221/…上有一个关于编写有缺陷的随机播放算法的警告性故事。为了编写d6()函数,将对其进行足够的测试,以确保它不会生成超出范围的数字,然后对分布进行卡方检验(卡方对伪随机序列相当敏感)。还要看一下串行相关系数。

“这依赖于随机函数总是在给定相同种子的情况下返回相同的值。但是,有时这是一个无效的假设。” 我点击了链接,但没有看到无效的假设。它很清楚地说:“如果重复使用相同的种子,则会生成相同的数字序列。”
Kyralessa

@Kyralessa“不能保证在.NET Framework的主要版本中,Random类中随机数生成器的实现不会保持相同。” 因此,这不是一个大问题,但仍然需要考虑。
dlras2

4
@Kyralessa我错过了那句话的重要部分:“结果,您的应用程序代码不应假定相同的种子将在不同版本的.NET Framework中产生相同的伪随机序列。”
dlras2 2012年

Answers:


102

我认为单元测试不是测试随机性的正确工具。单元测试应调用一个方法,并针对期望值测试返回的值(或对象状态)。测试随机性的问题在于,您想要测试的大多数东西都没有期望值。您可以使用给定的种子进行测试,但这仅测试可重复性。它没有提供任何方法来衡量分布的随机性,甚至根本不是随机的。

幸运的是,您可以运行许多统计测试,例如随机性测试。也可以看看:

  1. 如何对伪随机数生成器进行单元测试?

    • 史蒂夫·杰索普(Steve Jessop)建议您找到正在使用的同一RNG算法的经过测试的实现,并将其输出与所选种子和自己的实现进行比较。
    • Greg Hewgill建议ENT的统计测试套件。
    • John D. Cook向读者介绍了他的CodeProject文章“ 简单随机数生成”,其中包括在Donald Knuth的第2卷“半数值算法”中提到的Kolmogorov-Smirnov测试的实现。
    • 有人建议进行检验,以确保生成的数字分布均匀,进行卡方检验,并检验平均值和标准偏差在预期范围内。(请注意,仅测试分布是不够的。[1,2,3,4,5,6,7,8]是均匀分布,但肯定不是随机的。)
  2. 具有返回随机结果的函数的单元测试

    • Brian Genisio指出,模拟RNG是使测试可重复的一种选择,并提供C#示例代码。
    • 同样,还有更多的人指出使用固定的种子值来实现可重复性,并使用简单的测试来进行均匀分布,卡方等。
  3. 单元测试随机性是一篇Wiki文章,讨论了在尝试测试本质上不可重复的测试时已经涉及的许多挑战。我从中收集到的一个有趣的地方是:

    我曾经看到过winzip用作测量值文件的随机性的工具(显然,压缩文件越小,随机性就越小)。


另一个用于统计随机性的良好测试套件是在fourmilab.ch/random中找到的“ ent” 。

1
您可以总结一下您发布的一些链接,以确保答案的完整性吗?
dlras2

@DanRasmussen当然,我周末有时间这样做。
比尔蜥蜴2012年

4
“……随机性的问题是没有期望值……” –具有讽刺意味的是,鉴于“期望值”是统计中定义明确的术语。尽管这不是您的意思,但它暗示了正确的解决方案:使用统计分布的已知属性,结合随机抽样和统计测试,确定算法是否具有很高的概率。是的,这不是经典的单元测试,但我想提一下它,因为在最简单的情况下,它只是查看… 期望值的分布。
康拉德·鲁道夫2012年

2
Dieharder上著名的Diehard随机性测试电池有一个更新版本,其中包括由美国国家标准技术研究院(NIST)开发的统计测试套件(STS)。它可以在Ubuntu和可能的其他发行版中随时运行:phy.duke.edu/~rgb/General/dieharder.php
nealmcb 2014年

21

1.对您的算法进行单元测试

对于第一个问题,我将构建一个假类,为您提供一个随机数序列,您知道该序列的算法结果。这样,您可以确保在随机函数之上构建的算法能够正常工作。因此,遵循以下原则:

Random r = new RandomStub([1,3,5,3,1,2]);
r.random(); //returns 1
r.random(); //returns 3
...

2.看看您的随机函数是否有意义

在单元测试中,您应该添加可以多次运行的测试,并声明结果

  • 在您设置的边界内(因此,掷骰子在1到6之间)并且
  • 显示合理的分布(进行多次测试,看看分布是否在预期的x%之内,例如骰子掷骰,应该看到2分布的10%到20%(1/6 = 16.67%)时间(如果您将其滚动1000次)。

3.算法和随机函数的集成测试

您希望您的数组在原始排序中多久排序一次?对几百次排序,并断言只有x%的时间不会改变。

这实际上已经是一个集成测试,您正在与随机函数一起测试算法。一旦使用了真正的随机函数,就无法再进行一次单独的测试。

根据经验(我写了遗传算法),我想将算法的单元测试,随机函数的分布测试和集成测试结合起来是可行的方法。


14

似乎被遗忘的PRNG的一个方面是,其所有属性本质上都是统计性质的:您不能期望对数组进行改组将导致与您开始时的排列不同。基本上,如果您使用的是正常的PRNG,则唯一可以保证的是它不使用简单的模式(希望如此),并且在返回的一组数字之间均匀分布。

对PRNG进行适当的测试将使其至少运行100次,然后检查输出的分布(这是对问题第二部分的直接回答)。

第一个问题的答案几乎是相同的:使用{1、2,...,n}进行大约100次测试,并计算每个元素在每个位置的出现次数。如果改组方法有效,则它们应该大致相等。

完全不同的问题是如何测试加密级PRNG。除非您真的知道自己在做什么,否则您可能不应该考虑这个问题。众所周知,人们只需进行几次“优化”或琐碎的编辑操作即可破坏(阅读:打开灾难性漏洞)良好的密码系统。

编辑:我已经彻底重读了问题,最佳答案和我自己的。尽管我的观点仍然成立,但我还是会赞同比尔·蜥蜴的回答。单元测试本质上是布尔值-要么失败,要么成功,因此不适合测试PRNG(或使用PRNG的方法)的“良好”,因为对此问题的任何回答都是量化的,而不是极地。


1
我认为您的意思是每个元素在每个位置的出现次数应大致相等。如果它们始终完全相等,那就很不对劲。
2012年

@octern谢谢,我不知道该怎么写……到现在为止是完全错误的……
K.Steff 2012年

6

这有两个部分:测试随机化和测试使用随机化的事物。

测试随机化相对简单。您检查随机数生成器的周期是否符合您的预期(对于一些样本,使用一些随机种子的一些样本,在某个阈值内),并且输出在大样本量上的分布是否符合您的预期它是(在某个阈值内)。

最好使用确定性伪随机数生成器来测试使用随机化的事物。由于基于种子(其输入)知道随机化的输出,因此您可以根据输入与预期输出进行正常的单元测试。如果您的RNG 不是确定性的,请使用确定性(或不是随机的)模拟它。与使用它的代码隔离地测试随机化。


6

让它运行很多次并可视化您的数据

这是Coding Horror洗牌的示例,您可以看到算法是否正确:

在此处输入图片说明

很容易看到,每个可能的项目都至少返回了一次(边界确定)并且分配正常。


1
+1可视化是关键。我一直喜欢Block cipher article的ECB部分中带有企鹅图片的示例。自动软件很少能检测出这种规律
Maksee

嗯 可视化的目的是表明分布不正确。天真的洗牌算法使得某些订单比别人更容易。请注意2341、2314、2143和1342条向右延伸多远?
hvd

4

我发现通用指针在处理接受随机输入的代码时很有用:检查预期随机性的边缘情况(最大和最小值,以及最大+1和最小-1值,如果适用)。检查数字具有拐点(即-1、0、1或大于1,小于1且对于分数值可能使函数弄乱的情况为非负数)的位置(在上方,上方和下方)。完全在允许的输入范围之外检查几个位置。检查一些典型情况。您还可以添加随机输入,但是对于单元测试,它具有不良的副作用,即每次运行测试时都不会测试相同的值(尽管可以使用种子方法,但是可以测试种子中的前1,000个随机数S等)。

对于测试随机函数的输出,确定目标很重要。对于纸牌,目标是测试0-1随机生成器的均匀性,确定结果中是否出现了所有52张纸牌,还是某个其他目标(也许是此列表中的全部或更多)?

在特定示例中,您必须假定随机数生成器是不透明的(就像对OS syscall或malloc-进行单元测试没有意义,除非您编写OS)。测量随机数生成器可能很有用,但您的目标不是编写随机生成器,只是要看到您每次获得52张卡片,并且它们会改变顺序。

长话大说,这里实际上有两个测试任务:测试RNG是否产生正确的分布,以及检查您的洗牌代码是否正在使用该RNG产生随机结果。如果您正在编写RNG,请使用统计分析来证明您的分布,如果您正在编写洗牌机,请确保每个输出中有52张非重复的卡(这是通过检查使用的情况进行测试的更好案例) RNG)。


4

您可以依靠安全的随机数生成器

我只是有个可怕的想法:您不是在编写自己的随机数生成器,对吗?

假设您不是,那么您应该测试您负责的代码,而不是其他人的代码(例如SecureRandom框架的实现)。

测试你的代码

为了测试您的代码是否正确响应,通常使用低可见性方法来生成随机数,以便可以由单元测试类轻松覆盖它。这种重写的方法有效地模拟了随机数生成器,使您可以完全控制生成的内容和时间。因此,您可以充分行使自己的代码,这是单元测试的目标。

显然,您将检查边缘条件,并确保改组的发生完全按照算法在给定适当输入的情况下进行。

测试安全随机数生成器

如果不确定您的语言的安全随机数生成器不是真正的随机性还是有错误(提供超出范围的值等),那么您需要对输出进行数亿次迭代进行详细的统计分析。绘制每个数字的出现频率,它应该以相等的概率出现。如果结果以一种或另一种方式倾斜,则应将发现的结果报告给框架设计者。由于安全随机数生成器是许多加密算法的基础,因此他们肯定会对解决该问题感兴趣。


1

好吧,您永远不会百分百确定,因此您能做的最好的事情就是数字很可能是随机的。选择一个概率-假设在误差范围内,给定一百万个样本,数字或项目样本将出现x倍。将事物运行一百万次,看看它是否在利润范围内。幸运的是,计算机使这种事情变得容易实现。


但是,这样的单元测试是否被认为是好的做法?我一直认为单元测试应该尽可能简单:没有循环,分支或其他任何可以避免的事情。
dlras2 2012年

4
单元测试应该是正确的。如果需要分支,循环,递归-这就是代价。您无法使用单线单元测试对极其复杂,高度优化的类进行单元测试。我已经实现了Dijkstra的算法来对一个类进行一次单元测试。
K.Steff

3
@ K.Steff,哇。您是否对单元测试进行了单元测试以验证Dijkstra算法是否正确?
温斯顿·埃韦特

好的一点,事实上-是的,但是这次是“琐碎”的测试。但是,它们也是原始程序(A *)的单元测试。我认为这是一个非常好的做法-测试快速算法又会导致性能下降(但正确)。
K.Steff

1

为了测试随机数源生成的东西至少看起来具有随机性,我将让测试生成相当大的字节序列,将它们写入临时文件,然后将其封装Fourmilab的ent工具中。输入-t(简短)开关,这样它将生成易于解析的CSV。然后检查各种数字以查看它们是否“好”。

要确定哪些数字是好的,请使用已知的随机性源来校准您的测试。给定一组良好的随机数时,测试几乎应始终通过。因为即使是真正随机的序列也有可能生成看起来是非随机的序列,所以您无法获得肯定通过的测试。您只需选择一些阈值即可使随机序列不太可能导致测试失败。随机性不好玩吗?

注意:您不能编写表明PRNG生成“随机”序列的测试。您只能编写一个测试,如果通过则表明PRNG生成的序列是“随机”的概率。欢迎来到随机的喜悦!


1

情况1:测试随机播放:

考虑一个数组[0,1,2,3,4,5],将其洗牌,会出错吗?通常的东西:a)完全没有洗牌,b)洗牌1-5但不洗牌0,洗牌0-4而不洗牌5,洗牌,并且总是生成相同的图案,...

一项测试可以将它们全部捕获:

随机播放100次,然后在每个插槽中添加值。每个插槽的总和应彼此相似。可以计算平均/标准差。(5 + 0)/2=2.5,100*2.5 =25。例如,期望值约为25。

如果值超出范围,则极有可能出现假阴性。您可以计算出该机会有多大。重复测试。好吧-当然,机会很小,测试连续两次失败。但是您没有例程可以自动删除您的源代码,如果单元测试失败,您可以吗?再次运行!

它可能连续失败3次?也许您应该试试彩票。

情况2:掷骰子

掷骰问题是相同的问题。掷骰子6000次。

for (i in 0 to 6000) 
    ++slot [Random.nextInt (6)];
return (slot.max - slot.min) < threshold;
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.