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