测试python函数的非确定性行为



我们有一个庞大而复杂的函数,它需要具有确定性。它是我们公司的主力之一,涵盖了大量的代码。由于python的dict迭代器,此代码经常变得不确定。这种情况已经发生过很多次,很难追踪,而且往往不会立即引起注意。我们想写一个自动测试来检测非确定性,但我不确定如何做到

我们尝试过在循环中运行函数,测试结果总是相同的,但有时,即使函数是不确定的,由于dict迭代器的顺序任意但有些一致,函数也会通过测试。

有没有一种方法可以编写一个自动测试来捕捉这种错误?

也许有一种方法可以破解python的dict,使迭代器在测试过程中是随机的,而不是任意的?这样反复调用函数会更有可能产生分歧吗?这似乎是一个相当复杂的方法,但我想不出其他方法。

编辑:

我们目前使用的是Python 2.7。

我们有各种子模块的单元测试,但由于dict顺序的任意性但一致性,它们通常不会暴露出非确定性。

此外,也许非确定性并不是描述这个问题的正确方式。此函数接受{id:data},但是id的值不应该影响代码的结果,但是由于python dict排序,它有时会影响。也许测试这一点的最佳方法是用随机值替换id,并在多次使用不同的id运行后检查输出是否相同。

如果要随机化哈希种子,可以将-R标志指定为python:

-R     : use a pseudo-random salt to make hash() values of various types be
unpredictable between separate invocations of the interpreter, as
a defense against denial-of-service attacks

a la

~$ python -c "print {y:x for x,y in enumerate('foobar')}"
{'a': 4, 'r': 5, 'b': 3, 'o': 2, 'f': 0} #it will always be this
~$ python -R -c "print {y:x for x,y in enumerate('foobar')}"
{'a': 4, 'b': 3, 'r': 5, 'f': 0, 'o': 2}
~$ python -R -c "print {y:x for x,y in enumerate('foobar')}"
{'a': 4, 'b': 3, 'r': 5, 'o': 2, 'f': 0}
~$ python -R -c "print {y:x for x,y in enumerate('foobar')}"
{'f': 0, 'o': 2, 'b': 3, 'r': 5, 'a': 4}
~$ python -R -c "print {y:x for x,y in enumerate('foobar')}"
{'r': 5, 'f': 0, 'o': 2, 'a': 4, 'b': 3}

请注意,此行为是python 3.3中的默认行为。

您可以使用OrderedDict强制两个类似的dict具有不同的"顺序"。

使用这些作为代码的输入,而不是普通的dicts,您可以可靠地检查与dict订单问题有关的代码行为。

例如,此测试不时失败(相对较少):

d1 = {'a':1, 'b': 2}
d2 = dict(d1)
j1 = json.dumps(d1)
j2 = json.dumps(d2)
assert j1 == j2:

这个测试可以预见地失败了:

import json
from collections import OrderedDict
d1 = OrderedDict([('a', 1), ('b', 2)])
d2 = OrderedDict([('b', 2), ('a', 1)])
j1 = json.dumps(d1)
j2 = json.dumps(d2)
assert j1 == j2

然而,这可能更适合于对小函数进行单元测试。如果你同时测试一个"大而复杂的函数",那么dict很可能是在函数内部生成的,所以仅仅对输入进行操作是不够的。