Ruby:在代码中使用rand(),但编写测试来validation概率

我有一些代码可以根据加权随机提供的东西。 重量更重的东西更有可能随机选择。 现在我是一个很好的rubyist,我想用测试来覆盖所有这些代码。 我想测试根据正确的概率获取的东西。

那么我该如何测试呢? 为随机的东西创建测试会使实际与预期的比较变得非常困难。 我有一些想法,以及为什么它们不会很好用:

  • Stub Kernel.rand在我的测试中返回固定值。 这很酷,但rand()被多次调用,我不确定我是否可以通过足够的控制来测试它以测试我需要的东西。

  • 获取随机项的次数很多次,并将实际比率与预期比率进行比较。 但除非我可以无数次运行它,否则这将永远不会是完美的,如果我在RNG中运气不好,可能会间歇性地失败。

  • 使用一致的随机种子。 这使RNG可重复,但它仍然没有给我任何validation项目A将在80%的时间发生(例如)。

那么我可以使用什么样的方法来编写随机概率的测试覆盖率?

我认为你应该分开你的目标。 一个是你提到的存根Kernel.rand。 以rspec为例,你可以这样做:

test_values = [1, 2, 3] Kernel.stub!(:rand).and_return( *test_values ) 

请注意,除非使用Kernel作为接收器调用rand,否则此存根将不起作用。 如果你只是调用“rand”,那么当前的“self”将收到消息,你实际上会得到一个随机数而不是test_values。

第二个目标是执行类似于实际生成随机数的字段测试。 然后,您可以使用某种公差来确保接近所需的百分比。 这永远不会是完美的,并且可能需要人来评估结果。 但它仍然有用,因为您可能会意识到另一个随机数生成器可能更好,例如从/ dev / random读取。 此外,进行这种测试是很好的,因为假设您决定迁移到一种新的平台,其系统库不如生成随机性,或者在某个版本中存在一些错误。 测试可能是一个警告标志。

这真的取决于你的目标。 你只想测试你的加权算法,还是随机性?

最好是将Kernel.rand存根以返回固定值。

Kernel.rand不是你的代码。 您应该假设它有效,而不是尝试编写测试它而不是代码的测试。 使用您选择并明确编码的固定值集合比添加对特定种子生成的rand的依赖性更好。

如果你想沿着一致的种子路线走下去,看看Kernel#srand

http://www.ruby-doc.org/core/classes/Kernel.html#M001387

引用文档(重点补充):

将伪随机数生成器播种到number的值。 如果省略number或为零,则使用时间,进程ID和序列号的组合为发生器设定种子。 (如果在没有先前调用srand但没有序列的情况下调用Kernel :: rand,这也是行为。) 通过将种子设置为已知值,可以在测试期间使脚本成为确定性的。 返回先前的种子值。 另见Kernel :: rand。

对于测试,使用以下简单但完全合理的 LCPRNG来存根Kernel.rand:

 @@q = 0 def r @@q = 1_103_515_245 * @@q + 12_345 & 0xffff_ffff (@@q >> 2) / 0x3fff_ffff.to_f end 

如果您的代码兼容,您可能希望跳过除法并直接使用整数结果,因为结果的所有位都可以重复,而不仅仅是“大多数”。 这会将您的测试与“改进”隔离到Kernel.rand,并允许您测试您的分布曲线。