为什么成功的assertEqual并不总是意味着成功的assertItemsEqual?

时间:2015-04-17 03:15:55

标签: python python-unittest

assertItemsEqual表示assertEqual(sorted(expected), sorted(actual))"相当于assertItemsEqual"。在下面的示例中,除test4之外的所有测试都通过。为什么assertEqual在这种情况下会失败?

根据最不惊讶的原则,给定两个迭代,我希望成功的assertItemsEqual意味着成功的import unittest class foo(object): def __init__(self, a): self.a = a def __eq__(self, other): return self.a == other.a class test(unittest.TestCase): def setUp(self): self.list1 = [foo(1), foo(2)] self.list2 = [foo(1), foo(2)] def test1(self): self.assertTrue(self.list1 == self.list2) def test2(self): self.assertEqual(self.list1, self.list2) def test3(self): self.assertEqual(sorted(self.list1), sorted(self.list2)) def test4(self): self.assertItemsEqual(self.list1, self.list2) if __name__=='__main__': unittest.main()

FAIL: test4 (__main__.test)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "assert_test.py", line 25, in test4
    self.assertItemsEqual(self.list1, self.list2)
AssertionError: Element counts were not equal:
First has 1, Second has 0:  <__main__.foo object at 0x7f67b3ce2590>
First has 1, Second has 0:  <__main__.foo object at 0x7f67b3ce25d0>
First has 0, Second has 1:  <__main__.foo object at 0x7f67b3ce2610>
First has 0, Second has 1:  <__main__.foo object at 0x7f67b3ce2650>

----------------------------------------------------------------------
Ran 4 tests in 0.001s

FAILED (failures=1)

这是我机器上的输出:

{{1}}

2 个答案:

答案 0 :(得分:3)

文档的相关部分在这里:

https://docs.python.org/2/reference/expressions.html?highlight=ordering#not-in

  

内置类型的大多数其他对象比较不相等,除非它们是同一个对象;一个对象被认为是小于还是大于另一个对象的选择是在程序的一次执行中任意但一致的。

因此,如果您制作x, y = foo(1), foo(1),那么排序最终是x > y还是x < y还不明确。在python3中你根本不被允许,sorted调用应该引发异常。

由于unittest为每个测试方法调用setUp,因此每次都会创建不同的foo个实例。


assertItemsEqual是用collections.Counter(dict的子类)实现的,所以我认为test4的失败可能是这个事实的症状:

>>> x, y = foo(1), foo(1)
>>> x == y
True
>>> {x: None} == {y: None}
False

如果两个项目比较相等,那么它们应该散列相同的 ,否则您可能会破坏这样的映射。

答案 1 :(得分:3)

文档规范与实现有趣地分离,实现从不进行任何排序。 Here is the source code。如您所见,它首先尝试使用collections.Counter进行散列计数。如果失败并出现类型错误(因为任何一个列表都包含一个不可删除的项),它会移到a second algorithm,在那里使用python ==和O(n ^ 2)循环进行比较。

因此,如果您的foo类不可用,则第二个算法会发出匹配信号。但它完全可以清洗。来自文档:

  

默认情况下,作为用户定义类实例的对象是可清除的;它们都比较不等(除了它们自己),它们的哈希值来自它们的id()。

我通过致电collections.Counter([foo(1)])来验证这一点。没有类型错误异常。

所以这里是您的代码脱轨的地方。来自__hash__上的文档:

  

如果定义 cmp ()或 eq ()但不定义哈希(),则其实例将无法在哈希集合中使用。

不幸的是&#34;不可用&#34;显然不等于&#34;不可用。&#34;

接着说:

  

从父类继承哈希()方法但更改 cmp ()或 eq ()的含义的类,以便返回的哈希值不再合适(例如,通过切换到基于值的相等概念而不是基于默认身份的相等)可以通过在类中设置哈希 =无来明确地将自身标记为不可删除定义。

如果我们重新定义:

class foo(object):
    __hash__ = None
    def __init__(self, a):
        self.a = a
    def __eq__(self, other):
        return isinstance(other, foo) and self.a == other.a

所有测试都通过了!

所以看来这些文件并不完全错误,但它们也不是很清楚。他们应该提到计数是用散列完成的,只有当失败时才尝试简单的等式匹配。如果对象具有完整的哈希语义或完全不可用,则这只是一种有效的方法。你们处于中间地带。 (我相信Python 3更严格禁止或至少警告这种类型的类。)

相关问题