Python单元测试中assertDictContainsSubset的推荐替代方案

57
我有一些用unittest编写的Python测试。我想检查我的一些字典是否至少包含某些属性等于特定值,如果还有额外的值也没有关系。 assertDictContainsSubset非常完美,但是它已经被弃用了。有更好的替代品吗?或者如果目标字典中包含要检查的内容,我应该递归地断言它们相等吗?
文档建议使用addTypeEqualityFunc,但我有时确实想对字典使用普通的assertEqual。
4个回答

49

Python 3.9+ 版本中,可以使用字典合并操作符

更改

assertDictContainsSubset(a, b)

assertEqual(b, b | a)

对于旧版本的Python,请将其更改为

assertEqual(b, {**b, **a})

请注意参数的顺序assertDictContainsSubset将“较大”的字典(b)放在第二个位置,子集(a)放在第一位,但是将较大的字典(b)放在第一位更有意义(这就是为什么在第一次删除assertDictContainsSubset的原因)。

这将创建 b 的副本然后迭代 a,将任何键设置为它们在 a 中的值,然后将该结果与原始的 b 进行比较。如果您可以将 a 的所有键/值添加到 b 中,并且仍然具有相同的字典,则表示 a 不包含不在 b 中的任何键,并且它包含的所有键具有与在 b 中相同的值,即 ab 的子集。


如果要比较字典,请使用assertDictEqual而不是assertEqual,以获得更好的失败消息。 - Paul Price
5
@PaulPrice 《assertDictEqual》的文档(https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertDictEqual)中提到:“在调用`assertEqual()`时,默认将使用此方法比较字典”而且还说“通常不需要直接调用这些方法”。 - user3064538
按参数顺序,最好使用assertEqual(b, a | b)assertEqual(b, {**a, **b})。根据文档[https://peps.python.org/pep-0584/#specification],如果一个键在两个操作数中都出现,则以右操作数中的值为准。 - mosi_kha
1
这正是我们想要的。如果某个键对应的值在 a 中与 b 不同,则 a 不是 b 的子集。若要忽略这些值,请使用 a.keys().issubset(b.keys())a.keys() <= b.keys() - user3064538

15
如果您想测试字典A是否为字典B的子集,我认为我会编写一个函数,尝试从字典B中提取字典A的内容生成一个新的字典C,然后使用assertEqual(A,C)进行断言。
def extractDictAFromB(A,B):
    return dict([(k,B[k]) for k in A.keys() if k in B.keys()])

那么你就可以这样做

assertEqual(A,extractDictAFromB(A,B))

与我看到的其他答案相比,这个答案的好处在于它允许您获得 assertEqual(或 nosetests 中的 assert_equal)提供的良好报告。 - arhuaco
这里所谓的 extractDictAFromB 实际上就是 intersection - mehmet

14

在@bman的回答基础上进行扩展,利用类似于集合的对象的比较运算符被重载为子集运算符的特点,您可以使用assertGreaterEqual以获得(可以说是)更好的错误消息。

比较这两个测试:

import unittest

class SubsetTestCase(unittest.TestCase):
    def test_dict_1(self):
        a = {1: 1, 2: 2}
        b = {1: 2}
        self.assertTrue(a.items() >= b.items())

    def test_dict_2(self):
        a = {1: 1, 2: 2}
        b = {1: 2}
        self.assertGreaterEqual(a.items(), b.items())

unittest.main()

结果是:

======================================================================
FAIL: test_dict_1 (__main__.SubsetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 9, in test_dict_1
    self.assertTrue(a.items() >= b.items())
AssertionError: False is not true

======================================================================
FAIL: test_dict_2 (__main__.SubsetTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "test.py", line 15, in test_dict_2
    self.assertGreaterEqual(a.items(), b.items())
AssertionError: dict_items([(1, 1), (2, 2)]) not greater than or equal to dict_items([(1, 2)])

----------------------------------------------------------------------

使用 assertGreaterEqual,你可以从错误消息中查看两个字典的内容。


无法与Python 3一起使用。错误">="不支持字典实例和字典实例之间的比较 - Pramod Jangam
@PramodJangam 这个答案并不建议将 dictdict 进行比较,而是建议将 dict.items()dict.items() 进行比较。dict.items() 返回一个类似于集合的对象,可以相互比较。 - Ignatius

9

Andrew提供了一种使用assertEqual的解决方案。但是,为了让未来的读者更加清楚,以下有两个更简洁的替代方案。第一个方案使用set的issubset方法:

assert set(A.items()).issubset(set(B.items()))

但是还有一种更简单、更符合 Python 风格的方法来实现这个功能:
set(A.items()) <= set(B.items())

第二种解决方案的陷阱在于您无法知道子集中缺少哪些超集键。
但是,如果您的值内部有不可哈希变量(例如dict),则两种解决方案都将失败。

对于最后一行,您真的想要 set(A.items()) <= set(B.items()),否则如果我没记错的话,它会进行列表比较。最后,如果任何值不可哈希,则此方法将不起作用。 - Olivier Melançon
4
@OlivierMelançon 不需要使用 set(),因为dict.items()已经具备了集合的特性。详情请见:https://docs.python.org/3/library/stdtypes.html#dict-views - Ignatius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接