如何在 Unittest 上断言可迭代对象不为空?

How to assert that an iterable is not empty on Unittest?

向服务提交查询后,我得到字典或列表,我想确保它不为空。我使用 Python 2.7.

令我惊讶的是 unittest.TestCase class 实例没有任何 assertEmpty 方法。

现有的替代方案看起来不对:

self.assertTrue(bool(d))
self.assertNotEqual(d,{})
self.assertGreater(len(d),0)

Python unittest 框架是否缺少这种方法?如果是,那么断言可迭代对象不为空的最 pythonic 方法是什么?

也许:

self.assertRaises(StopIteration, next(iterable_object))

空 lists/dicts 评估为 False,因此 self.assertTrue(d) 完成了工作。

具体取决于您要查找的内容。

如果你想确保对象是可迭代的且不为空:

# TypeError: object of type 'NoneType' has no len()
# if my_iterable is None
self.assertTrue(len(my_iterable))

如果被测对象可以None:

self.assertTrue(my_maybe_iterable)

Python

中的“虚假”值

A falsy (sometimes written falsey) value is a value that is considered false when encountered in a Boolean context.

根据 official doc,以下内置类型的计算结果为 false:

  • 常量定义为假:NoneFalse
  • 任何数字类型的零:00.00jDecimal(0)Fraction(0, 1)
  • 空序列和集合:''()[]{}set()range(0)

因此,可以检查

(官方文档有所有可用的完整列表 assert methods。)

清洁代码

所有这些 assertTrue()assertFalse() 调用都具有误导性,因为我们想要检查是否为空,并且需要知道哪些类型的计算结果为 false 才能正确理解其中发生的事情测试。

所以,为了干净的代码和更好的可读性,我们可以简单地定义我们自己的 assertEmpty()assertNotEmpty() 方法,如下所示:

def assertEmpty(self, obj):
    self.assertFalse(obj)

def assertNotEmpty(self, obj):
    self.assertTrue(obj)

这一切都归功于 winklerrr,我只是在扩展他的想法:在需要 assertEmpty 或 assertNotEmpty 时使用可导入的 mixins:

class AssertEmptyMixin( object ):
    def assertEmpty(self, obj):
        self.assertFalse(obj)

class AssertNotEmptyMixin( object ):
    def assertNotEmpty(self, obj):
        self.assertTrue(obj)

注意,mixins 应该放在左边:

class MyThoroughTests( AssertNotEmptyMixin, TestCase ):
    def test_my_code( self ):
        ...
        self.assertNotEmpty( something )

基于@winklerr 的回答和@Merk 的评论,我首先扩展了检查给定对象是否为 Container 的想法。

from typing import Container


def assertContainerEmpty(self, obj: Container) -> None:
    """Asserts whether the given object is an empty container."""
    self.assertIsInstance(obj, Container)
    self.assertFalse(obj)

def assertContainerNotEmpty(self, obj: Container) -> None:
    """Asserts whether the given object is a non-empty container."""
    self.assertIsInstance(obj, Container)
    self.assertTrue(obj)

这意味着如果给定的对象是例如 assertEmptyassertNotEmpty 将始终失败。 float 或用户定义的实例 class - 无论它是否会正确计算为 True/False.

与已经提出的答案略有不同...如果绝对需要特定的命名断言,您可以子class TestCase 并在其中添加新断言的方法。

from pathlib import Path
from typing import Container
from unittest import TestCase

class BaseTestCase(TestCase):
    def assertIsFile(self, path: str, msg: str=None) -> None:
        default_msg = 'File does not exist: {0}'.format(path)
        msg = msg if msg is not None else default_msg
        if not Path(path).resolve().is_file():
            raise AssertionError(msg)
    
    def assertIsEmpty(self, obj: Container, msg: str=None) -> None:
        default_msg = '{0} is not empty.'.format(obj)
        msg = msg if msg is not None else default_msg
        self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
        if len(obj) > 0:
            raise AssertionError(msg)
    
    def assertIsNotEmpty(self, obj: Container, msg: str=None) -> None:
        default_msg = '{0} is empty.'.format(obj)
        msg = msg if msg is not None else default_msg
        self.assertIsInstance(obj, Container, '{0} is not a container.'.format(obj))
        if obj is None or len(obj) == 0:
            raise AssertionError(msg)

然后子class 新的 BaseTestCase class 以使用新的断言方法。

class TestApplicationLoadBalancer(_BaseTestCase):

    def setUp(self) -> None:

        # These assertions will fail.
        self.assertIsFile('does-not-exist.txt')
        self.assertIsEmpty(['asdf'])
        self.assertIsNotEmpty([])

就像 built-in unittest 断言一样,如果需要,您可以向这些断言传递错误消息。

class TestApplicationLoadBalancer(_BaseTestCase):

    def setUp(self) -> None:

        # These assertions will fail.
        self.assertIsFile('does-not-exist.txt', 'Foo')
        self.assertIsEmpty(['asdf'], 'Bar')
        self.assertIsNotEmpty([], 'Baz')