
Unit testing a method that can have random behaviour今天下午我遇到了这种情况,所以我想问问你们做什么。 我们有一个用于重置用户密码的随机密码生成器,在解决它的问题时,我决定将例程转移到我的(缓慢增长的)测试工具中。 我想测试生成的密码是否符合我们设定的规则,但是当然,该函数的结果将是随机的(或者,是伪随机的)。 你们在单元测试中会做什么?生成一堆密码,检查它们是否全部通过并认为足够好? 单元测试在每次运行时都应该做同样的事情,否则您可能会遇到单元测试仅偶尔失败的情况,这可能是调试的真正痛苦。 尝试每次使用相同的种子为您的伪随机化器播种(在测试中,即-在生产代码中不是)。这样,您的测试每次都会生成同一组输入。 如果您无法控制种子并且没有办法防止您正在测试的功能被随机化,那么我想您将陷入不可预测的单元测试。 :( 该函数是一个假设,即对于所有输入,输出都符合规范。单元测试是一种伪造该假设的尝试。因此,是的,在这种情况下,您最好的做法是生成大量输出。如果它们都通过了您的规范,那么您可以合理地确定您的函数可以按照指定的方式工作。 请考虑将随机数生成器放在此函数之外,然后将一个随机数传递给该函数,以使函数具有确定性,而不是让它直接访问随机数生成器。这样,您可以在测试工具中生成大量随机输入,将它们全部传递给函数,然后测试输出。如果失败,请记录该值是什么,以便您有记录在案的测试用例。 除了测试一些以确保它们通过之外,我还要编写一个测试以确保破坏规则的密码失败。 代码库中是否有任何东西可以检查生成的密码,以确保它们足够随机?如果没有,我可能会考虑创建逻辑来检查生成的密码,进行测试,然后可以指出随机密码生成器正在工作(因为"错误"的密码不会消失)。 一旦有了该逻辑,就可以编写一个集成类型测试,该测试将生成大量密码并将其传递给逻辑,这时您将了解随机密码生成的"效果"如何。 首先,为PRNG使用种子。您的输入不再是随机的,并且摆脱了不可预测的输出的问题-即现在您的单元测试是确定性的。 但是,这不能解决测试实现的问题,但这是一个如何测试依赖于随机性的典型方法的示例。 想象一下,我们实现了一个函数,该函数收集红色和蓝色大理石的集合并随机选择一个,但是可以为该概率分配权重,即权重2和1表示红色大理石的可能性为两倍。被选为蓝色大理石。 我们可以通过将一个选项的权重设置为零并验证在所有情况下(实际上,对于大量测试输入)我们总是得到例如蓝色大理石。然后,权重反转应得出相反的结果(所有红色大理石)。 这不能保证我们的功能符合预期(如果我们传递相同数量的红色和蓝色大理石并且重量相等,那么在大量试验中我们总是得到50/50的分布吗?),但是练习通常就足够了。 使用固定的随机种子或使其具有可重现性(即:从当日衍生) 以我的拙见,您不希望测试有时会通过,有时会失败。某些人甚至可能认为这种测试不是单元测试。但是主要的想法是,当您看到绿色的条时,请确保该功能正常。 牢记此原则,您可以尝试执行合理的次数,以使错误更正的机会几乎为零。但是,测试的任何一次失败都将使您除调试失败外,还要进行更广泛的测试。 在不知道规则是什么的情况下很难确定,但是假设它们类似"密码必须至少包含8个字符,并至少包含一个大写字母,一个小写字母,一个数字和一个特殊字符。"那么即使使用蛮力检查生成的密码以证明算法正确也是不可能的(因为这需要超过8 ^ 70 = 1.63x10 ^ 63次检查,具体取决于您指定使用多少个特殊字符,需要非常非常长的时间才能完成)。 最终,您所能做的就是测试尽可能多的密码,如果任何密码违反了规则,那么您知道该算法是错误的。最好的办法可能是让它整夜运行,如果早晨一切都好,您可能会没事的。 如果要在生产中加倍确定,请实现一个外部函数,该函数在循环中调用密码生成函数并对照规则进行检查。如果失败,则记录一条错误消息指出这一点(因此您知道需要修复)并生成另一个密码。继续直到获得符合规则的一个。 我假设用户输入的密码符合与随机生成的密码相同的限制。因此,您可能希望拥有一组用于检查已知条件的静态密码,然后您将具有一个执行动态密码检查的循环。循环的大小并不是很重要,但是它应该足够大,以使您从生成器中获得温暖的模糊感,但又不要太大,以至于测试将永远运行。如果随着时间的流逝出现了任何问题,您可以将这些案例添加到您的静态列表中。 从长远来看,弱密码不会破坏您的程序,而密码安全性则落在用户手中。因此,您的首要任务是确保动态生成和强度检查不会破坏系统。 您可以为随机数生成器添加一个恒定值,以获取非随机结果并测试这些结果。 您还可以研究突变测试(Java的Jester,Ruby的Heckle) 好吧,考虑到它们是随机的,没有真正的方法可以确保,但是测试10万个密码应该可以消除大多数疑问:) |