如何解决Ruby:在代码中使用rand,但编写测试来验证概率
|| 我有一些代码可以根据加权随机数传送内容。重量更大的东西更有可能被随机选择。现在,作为一名优秀的红宝石专家,我想用测试覆盖所有这些代码。我想测试事物是否已根据正确的概率获取。 那么我该如何测试呢?为应该是随机的事物创建测试使得很难比较实际与预期。我有一些想法,以及为什么它们行不通: 在我的测试中,将存根Kernel.rand返回固定值。这很酷,但是rand()被多次调用,我不确定我是否可以使用足够的控件来绑定它来测试所需。 提取随机项目的次数非常大,然后将实际比率与预期比率进行比较。但是,除非我可以无限次运行它,否则它将永远不会是完美的,并且如果我在RNG中遇到不幸的话,可能会间歇性地失败。 使用一致的随机种子。这样可以使RNG可重复,但是仍然无法提供任何证据证明A项目将在80%的时间发生(例如)。 那么,我可以使用哪种方法为随机概率编写测试覆盖率?解决方法
我认为您应该分开目标。正如您提到的,其中之一是对Kernel.rand进行存根。例如,使用rspec,您可以执行以下操作:
test_values = [1,2,3]
Kernel.stub!(:rand).and_return( *test_values )
请注意,除非您使用内核作为接收者调用rand,否则此存根将无法工作。如果您仅调用\“ rand \”,则当前的\“ self \”将收到该消息,并且您实际上将获得一个随机数而不是test_values。
第二个目标是做一些像现场测试这样的事情,实际上您会生成随机数。然后,您将使用某种公差来确保您接近所需的百分比。但是,这永远不会是完美的,并且可能需要人工来评估结果。但是这样做仍然很有用,因为您可能意识到另一个随机数生成器可能更好,例如从/ dev / random中读取。另外,进行这种测试也是一件好事,因为假设您决定迁移到一种新的平台,该平台的系统库不擅长生成随机性,或者存在一些错误。某个版本。该测试可能是警告信号。
这确实取决于您的目标。您只想测试加权算法,还是随机性?
,最好对Kernel.rand存根以返回固定值。
Kernel.rand不是您的代码。您应该假定它有效,而不是尝试编写测试它而不是代码的测试。而且,使用您选择并明确编码的一组固定值比添加对rand为特定种子产生的值的依赖更好。
,如果您想沿着一致的种子路线前进,请看Kernel#srand
:
http://www.ruby-doc.org/core/classes/Kernel.html#M001387
引用文档(添加了重点):
播出伪随机数
生成值的数值。如果
数字被省略或为零,则将
发电机结合使用
时间,进程ID和序列
数。 (如果
Kernel :: rand被称为
以前叫srand,但没有
顺序。)通过设置种子
达到已知值,可以制作脚本
在测试过程中具有确定性。的
返回先前的种子值。也
参见Kernel :: rand。
,为了进行测试,请将Kernel.rand与以下简单但完全合理的LCPRNG存根在一起:
@@q = 0
def r
@@q = 1_103_515_245 * @@q + 12_345 & 0xffff_ffff
(@@q >> 2) / 0x3fff_ffff.to_f
end
如果您的代码兼容,则可能要跳过除法并直接使用整数结果,因为结果的所有位将是可重复的,而不仅仅是“大多数”。这样可以将测试从“改进”隔离到Kernel.rand,并应允许您测试分布曲线。
,我的建议:结合#2和#3。设置一个随机种子,然后大量运行测试。
我不喜欢#1,因为这意味着您的测试与实现紧密相关。如果更改rand()输出的使用方式,即使结果正确,测试也会中断。单元测试的重点是您可以重构该方法并依靠该测试来验证它仍然可以工作。
选项#3本身与#1存在相同的问题。如果更改rand()的使用方式,则会得到不同的结果。
选项#2是拥有不依赖内部知识的真正黑匣子解决方案的唯一方法。如果运行足够多次,则随机故障的可能性可以忽略不计。 (您可以挖出一名统计老师来帮助您计算“足够高”,或者您可以选择一个非常大的数字。)
但是,如果您过于挑剔并且“忽略不计”不够好,则将#2和#3结合使用将确保一旦测试开始通过,它将继续通过。即使只有微不足道的失败风险,也只有在您触摸被测代码时才会冒出来。只要您不理会代码,就可以保证测试将始终正常运行。
,通常,当我需要从随机数中得出的结果可预测的结果时,我通常希望控制RNG,这意味着最简单的方法是使其可注射。尽管可以重写/存根rand
,但是Ruby提供了一种很好的方式来向您的代码传递带有一些值的RNG:
def compute_random_based_value(input_value,random: Random.new)
# ....
end
然后将我在测试中当场制作的随机对象注入已知种子:
rng = Random.new(782199) # Scientific dice roll
compute_random_based_value(your_input,random: rng)
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。