【问题标题】:Testing for non-deterministic behavior of python function测试python函数的非确定性行为
【发布时间】:2014-03-22 00:53:30
【问题描述】:

我们有一个需要确定性的大而复杂的函数。它是我们公司的主力之一,涵盖了大量的代码。由于 python 的 dict 迭代器,此代码通常变得不确定。这种情况发生了很多次,很难追查到,而且往往不会立即注意到。我们想编写一个自动化测试来检测非确定性,但我不知道该怎么做。

我们曾尝试在循环中运行该函数,并且测试结果始终相同,但有时,即使该函数是非确定性的,该函数也会通​​过此测试,因为该函数的顺序是任意但有些一致的dict 迭代器。

有没有办法编写自动化测试来捕捉这种错误?

也许有一种方法可以破解 python 的 dict,以便在此测试期间迭代器是随机的而不是任意的?这样重复调用函数就更有可能出现分歧?这似乎是一个相当复杂的方法,但我想不出任何其他方法。

编辑:

我们目前使用的是 Python 2.7。

我们对各种子模块进行了单元测试,但是由于 dict 顺序的任意但一致的性质,它们通常不会暴露不确定性。

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

【问题讨论】:

  • 此行为取决于您运行的 python 版本。 Python 2 是任意但一致的,python 3.3+ 随机化哈希迭代顺序(安全原因)。
  • 重构并覆盖单元测试。如果你展示你的代码,答案可能更具体。

标签: python dictionary iteration non-deterministic


【解决方案1】:

您可以使用OrderedDict 强制两个相似的dicts 具有不同的“顺序”。

使用这些作为代码的输入而不是普通的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

但是,这可能更适合于对小型函数进行单元测试。如果您同时测试一个“大型而复杂的函数”,那么dicts 很可能是在函数内部生成的,因此对输入进行操作是不够的。

【讨论】:

    【解决方案2】:

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

    -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
    

    来啦

    ~$ 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 中的默认行为。

    【讨论】:

    • 这是完美的。现在我只希望我们所依赖的库都不会破坏这个选项。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-13
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多