测试Python中的实际相等性

时间:2014-06-17 16:00:03

标签: python unit-testing equality python-2.x

我正在编写一个模拟某个库的Python2模块。结果可能是floatintlongunicodestrtuplelist和自定义对象。列表可能不包含列表,但它们可能包含元组。元组可能不包含列表或元组。否则,列表和元组可能包含上面列出的任何其他类型。

(实际上,模块应该不会返回longstr,但如果确实如此,那么与{{1}相比,它们应该被捕获并报告为不同分别和int。)

我正在编写一个测试程序,通过我的模块试图模拟的库来检查已知答案的结果。显而易见的答案是测试值和类型,但我面临的一个问题是,在极端情况下,可能要测试的结果是unicode(应该与-0.0区分开来)和0.0(不是数字 - 浮点数可以采用的值)。

然而:

NaN

>>> a = float('nan') >>> b = float('nan') >>> a == b False >>> c = float('-0.0') >>> c -0.0 >>> d = 1.0 - 1.0 >>> c == d True 运算符没有帮助:

is

>>> a is b False >>> d is 0.0 False 有帮助:

repr

但仅限于某一点,因为它对对象没有帮助:

>>> repr(a) == repr(b)
True
>>> repr(c) == repr(d)
False
>>> repr(d) == repr(0.0)
True

但这有效:

>>> class e:
...   pass
... 
>>> f = e()
>>> g = e()
>>> f.x = float('nan')
>>> g.x = float('nan')
>>> f == g
False
>>> repr(f) == repr(g)
False

但它失败了,有元组和列表:

>>> repr(f.__dict__) == repr(g.__dict__)
True

我似乎很接近,所以我需要知道:

  1. 是否有更简单的方法来检查没有转换为字符串负担的实际相等性?
  2. 如果没有,我将如何比较包含对象的列表或元组?
  3. 编辑:要明确,我所追求的是完整的比较功能。我的测试函数大致如下:

    >>> h = [float('nan'), f]
    >>> i = [float('nan'), g]
    >>> h == i
    False
    >>> repr(h) == repr(i)
    False
    >>> repr(h.__dict__) == repr(i.__dict__)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: 'list' object has no attribute '__dict__'
    

    我的问题涉及到whatequal()应该是什么样的。

    编辑2:我发现Python标准模块 unittest 但很遗憾没有一项检查涵盖了这个用例,所以看来如果我打算使用它,我应该使用像>>> def test(expression, expected): ... actual = eval(expression) ... if not reallyequal(actual, expected): ... report_error(expression, actual, expected) 这样的东西。

    我真的很惊讶,在结果中嵌入预期的NaN和零点的单元测试是如此困难。我仍在使用repr解决方案,这是一个半解决方案,但我对其他想法持开放态度。

2 个答案:

答案 0 :(得分:1)

这是一个实现:

def really_equal(actual, expected, tolerance=0.0001):
    """Compare actual and expected for 'actual' equality."""

    # 1. Both same type?
    if not isinstance(actual, type(expected)):
        return False

    # 2. Deal with floats (edge cases, tolerance)
    if isinstance(actual, float):
        if actual == 0.0:
            return str(actual) == str(expected)
        elif math.isnan(actual):
            return math.isnan(expected)
        return abs(actual - expected) < tolerance

    # 3. Deal with tuples and lists (item-by-item, recursively)
    if isinstance(actual, (tuple, list)):
        return all(really_equal(i1, i2) for i1, i2 in zip(actual, expected))

    # 4. Fall back to 'classic' equality
    return actual == expected

“经典”平等中的一些边缘案例:

>>> float('nan') == float('nan')
False
>>> really_equal(float('nan'), float('nan'))
True

>>> 0.0 == -0.0
True
>>> really_equal(0.0, -0.0)
False

>>> "foo" == u"foo"
True
>>> really_equal("foo", u"foo")
False

>>> 1L == 1
True
>>> really_equal(1L, 1)
False

类应该实现自己的__eq__“魔术方法”以确定两个实例是否相等 - 它们将落到# 4并在那里进行比较:

>>> class Test(object):

    def __init__(self, val):
        self.val = val

    def __eq__(self, other):
        return self.val == other.val


>>> a = Test(1)
>>> b = Test(1)
>>> really_equal(a, b)
True

答案 1 :(得分:0)

从答案和评论中可以清楚地看出,我的第一个问题的答案(是否比使用repr()更简单?)是否定的,没有更简单的方法。所以我已经研究了如何尽可能简单地完成这项工作,并且我已经提出了解决我的第二个问题的解决方案。

repr()在很大程度上起作用,但在自定义类的对象上失败。由于自定义对象的默认repr()无论如何都无用于任何有意义的目的,我所做的是覆盖每个基类的__repr__方法,如下所示:

class MyClass:
    def __repr__(self):
        return self.__class__.__name__ + "(" \
            + repr(sorted(self.__dict__.items(), key=lambda t: t[0])) + ")"

现在我可以对任何值使用repr()并获得一个实际上代表这些值的表达式,这是我的测试程序可以捕获的。

def reallyequal(actual, expected):
    return repr(actual) == repr(expected)

(由于其简单性,我将实际嵌入测试功能中)。

这是在行动:

>>> reallyequal(-0.0, 0.0)
False
>>> reallyequal(float('nan'),float('nan'))
True
>>> f = MyClass()
>>> f.x = float('nan')
>>> g = MyClass()
>>> g.x = float('nan')
>>> reallyequal(f, g)
True
>>> h = [f,3]
>>> i = [g,4]
>>> reallyequal(h, i)
False
>>> i[1] = 3
>>> reallyequal(h, i)
True
>>> g.x = 1
>>> reallyequal(h, i)
False
>>> f.x = 1L
>>> reallyequal(h, i)
False
>>> f.x = 1
>>> reallyequal(h, i)
True

修改:已编辑以纳入评论者的建议,并使用__dict__重新生成结果。