【发布时间】:2010-09-10 10:07:24
【问题描述】:
今天下午我遇到了这种情况,所以我想问问你们是怎么做的。
我们有一个用于用户密码重置的随机密码生成器,在解决它的问题时,我决定将例程移到我的(缓慢增长的)测试工具中。
我想测试生成的密码是否符合我们制定的规则,但当然函数的结果将是随机的(或者,好吧,是伪随机的)。
你们会在单元测试中做什么?生成一堆密码,检查它们都通过并认为足够好?
【问题讨论】:
标签: unit-testing random
今天下午我遇到了这种情况,所以我想问问你们是怎么做的。
我们有一个用于用户密码重置的随机密码生成器,在解决它的问题时,我决定将例程移到我的(缓慢增长的)测试工具中。
我想测试生成的密码是否符合我们制定的规则,但当然函数的结果将是随机的(或者,好吧,是伪随机的)。
你们会在单元测试中做什么?生成一堆密码,检查它们都通过并认为足够好?
【问题讨论】:
标签: unit-testing random
首先,为您的 PRNG 使用种子。您的输入不再是随机的,并且消除了不可预测的输出问题 - 即现在您的单元测试是确定性的。
然而,这并不能解决测试实现的问题,但这里有一个示例,说明如何测试依赖随机性的典型方法。
假设我们已经实现了一个函数,该函数采用一组红色和蓝色弹珠并随机挑选一个,但可以为概率分配一个权重,即 2 和 1 的权重意味着红色弹珠的可能性是其两倍像蓝色的弹珠一样被采摘。
我们可以通过将一个选项的权重设置为零并验证在所有情况下(在实践中,对于大量测试输入)我们总是得到例如蓝色弹珠。反转权重应该会得到相反的结果(所有红色弹珠)。
这并不能保证我们的函数按预期运行(如果我们传入相同数量的红色和蓝色弹珠并且具有相同的权重,我们是否总是在大量试验中得到 50/50 的分布?)但是在经常练习就足够了。
【讨论】:
使用固定的随机种子或使其可重现(即:从当天派生)
【讨论】:
以我的拙见,您不希望有时通过有时失败的测试。有些人甚至可能认为这种测试不是单元测试。但主要的想法是当你看到绿条时确保功能正常。
牢记这一原则,您可以尝试执行合理的次数,以使错误正确的机会几乎为零。然而,一个单一的测试失败将迫使您在调试失败之外进行更广泛的测试。
【讨论】:
在不知道您的规则的情况下很难确定,但假设它们类似于“密码必须至少为 8 个字符,其中至少有一个大写字母、一个小写字母、一个数字和一个特殊字符" 那么即使使用蛮力检查生成的足够数量的密码来证明算法是正确的也是不可能的(因为这将需要超过 8^70 = 1.63x10^63 的检查,具体取决于您指定使用的特殊字符的数量,这将需要很长时间才能完成)。
最终,您所能做的就是尽可能多地测试密码,如果有任何违反规则,那么您就知道算法不正确。最好的办法可能是让它在一夜之间运行,如果早上一切顺利,您可能会没事。
如果您想在生产中加倍确定,请实现一个外部函数,该函数在循环中调用密码生成函数并根据规则检查它。如果失败,则记录一个指示此错误的错误(因此您知道需要修复它)并生成另一个密码。继续,直到你得到一个符合规则的。
【讨论】:
我假设用户输入的密码符合与随机生成的密码相同的限制。因此,您可能需要一组静态密码来检查已知条件,然后您将有一个循环来执行动态密码检查。循环的大小不是很重要,但它应该足够大,以便您从生成器中获得那种温暖的模糊感觉,但又不能太大,以至于您的测试需要永远运行。如果随着时间的推移出现任何问题,您可以将这些案例添加到静态列表中。
但从长远来看,弱密码不会破坏您的程序,密码安全性掌握在用户手中。因此,您的首要任务是确保动态生成和强度检查不会破坏系统。
【讨论】:
除了测试一些以确保它们通过之外,我还会编写一个测试以确保违反规则的密码失败。
代码库中是否有任何东西检查生成的密码以确保它们足够随机?如果没有,我可能会考虑创建逻辑来检查生成的密码,对其进行测试,然后您可以声明随机密码生成器正在工作(因为“坏”密码不会出现)。
一旦您掌握了该逻辑,您就可以编写一个集成类型测试,该测试将生成大量密码并将其传递给逻辑,此时您就会了解您生成的随机密码有多“好” .
【讨论】:
函数是一个假设,即对于所有输入,输出都符合规范。单元测试是试图证伪该假设。所以是的,在这种情况下你能做的最好的事情就是产生大量的输出。如果它们都通过了您的规范,那么您可以合理地确定您的功能按规定工作。
考虑将随机数生成器放在此函数之外并向其传递一个随机数,使函数具有确定性,而不是让它直接访问随机数生成器。这样,您可以在测试工具中生成大量随机输入,将它们全部传递给您的函数,然后测试输出。如果失败,请记录该值是什么,以便您拥有记录在案的测试用例。
【讨论】:
您可以为随机数生成器设置一个常数值,以获得非随机结果并测试这些结果。
【讨论】:
单元测试在每次运行时都应该做同样的事情,否则你可能会遇到单元测试只是偶尔失败的情况,这可能是调试的真正痛苦。
尝试每次都为您的伪随机发生器播种相同的种子(在测试中,即——不是在生产代码中)。这样,您的测试每次都会生成相同的输入集。
如果您无法控制种子并且无法阻止您正在测试的函数被随机化,那么我猜您会遇到不可预测的单元测试。 :(
【讨论】:
好吧,考虑到它们是随机的,没有办法确定,但测试 100 000 个密码应该可以消除大多数疑问 :)
【讨论】: