16.2.2 处理不可预测的输出
还有一些情况可能无法预测准确的输出,不过仍可以测试。例如,每次运行测试时本地日期和时间值以及对象ID会改变,浮点值表示中使用的默认精度取决于编译器选项,另外容器对象(如字典)的串表示可能是不确定的。尽管这些条件可能无法控制,但是确实有一些技术可以处理。
例如,在CPython中,对象标识符是基于保存这个对象的数据结构的内存地址。
class MyClass:
pass
def unpredictable(obj):
"""Returns a new list containing obj.
>>> unpredictable(MyClass())
[<doctest_unpredictable.MyClass object at 0x10055a2d0>]
"""
return [obj]
每次程序运行时,这些ID值都会改变,因为这些值会被加载到内存的不同部分。
测试的值可能会以不可预测的方式改变时,如果具体值对于测试结果并不重要,则可以使用ELLIPSIS选项告诉doctest忽略验证值的某些部分。
class MyClass:
pass
def unpredictable(obj):
"""Return a new list containing obj.
>>> unpredictable(MyClass()) #doctest: +ELLIPSIS
[<doctest_ellopsis.MyClass object at 0x...>]
"""
return [obj]
unpredictable()调用后面的#doctest: +ELLIPSIS注释告诉doctest打开这个测试的ELLIPSIS选项。…将替换对象ID中的内存地址,这样就会忽略期望值中的这一部分。时间输出将成功匹配,并且通过测试。
有些情况下,不能忽略不可预测的值,因为这会让测试不完备或不准确。例如,处理一些数据类型时,如果其数据的串表示不一致,那么简单的测试很快会变得越来越复杂。举例来说,字典的串形式可能会根据增加键的顺序不同而改变。
keys = ['a','aa','aaa']
print('dict:',{k:len(k) for k in keys})
print('set :',set(keys))
由于散列随机化和键冲突,每次运行这个脚本时,字典的内部键列表的顺序都会有所不同。集合(set)会使用相同的散列算法,并提供相同的行为。
要处理这些潜在的差异,最好的方法是创建测试来生成不太可能改变的值。对于字典和集合,这可能意味着需要分布查找特定的键,并生成数据结构内容的一个有序列表,或者与一个字面值比较相等性而不依赖于串表示。
import collections
def group_by_length(words):
"""Returns a dictionary grouping words into sets by length.
>>> grouped = group_by_length(['python','module','of',
... 'the','week'])
>>> grouped == {2:set(['of']),
... 3:set(['the']),
... 4:set(['week']),
... 6:set(['python','module']),
... }
True
"""
d = collections.defaultdict(set)
for word in words:
d[len(word)].add(word)
return d
以上代码中的这个例子实际上会被解释为两个单独的测试,第一个没有任何控制台输出,第二个则会得到比较操作的布尔结果。