3.3 断言集合类型
断言列表、元组、字典和集合等类型在测试中也是很常见的,对于具有嵌套的集合数据,pytest的assert依然能够精确地显示出来出错的位置。比如下面这段测试用例代码:
class TestCollections(object): def test_dict(self): assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0} def test_dict2(self): assert {"a": 0, "b": {"c": 0}} == {"a": 0, "b": {"c": 2}} def test_list(self): assert [0, 1, 2] == [0, 1, 3] def test_list2(self): assert [0, 1, 2] == [0, 1, [1, 2]] def test_set(self): assert {0, 10, 11, 12} == {0, 20, 21}
执行上面的测试代码,核心输出会是下面这样:
============================================================ FAILURES ============================================================ ___________________________________________________ TestCollections.test_dict ____________________________________________________ self = <test_assertions.TestCollections object at 0x10b0d2d10> def test_dict(self): > assert {"a": 0, "b": 1, "c": 0} == {"a": 0, "b": 2, "d": 0} E AssertionError: assert {'a': 0, 'b': 1, 'c': 0} == {'a': 0, 'b': 2, 'd': 0} E Omitting 1 identical items, use -vv to show E Differing items: E {'b': 1} != {'b': 2} E Left contains 1 more item: E {'c': 0} E Right contains 1 more item: E {'d': 0}... E E ...Full output truncated (6 lines hidden), use '-vv' to show tests/test_assertions.py:27: AssertionError ___________________________________________________ TestCollections.test_dict2 ___________________________________________________ self = <test_assertions.TestCollections object at 0x10b0d2a90> def test_dict2(self): > assert {"a": 0, "b": {"c": 0}} == {"a": 0, "b": {"c": 2}} E AssertionError: assert {'a': 0, 'b': {'c': 0}} == {'a': 0, 'b': {'c': 2}} E Omitting 1 identical items, use -vv to show E Differing items: E {'b': {'c': 0}} != {'b': {'c': 2}} E Full diff: E - {'a': 0, 'b': {'c': 2}} E ? ^ E + {'a': 0, 'b': {'c': 0}}... E E ...Full output truncated (2 lines hidden), use '-vv' to show tests/test_assertions.py:30: AssertionError ___________________________________________________ TestCollections.test_list ____________________________________________________ self = <test_assertions.TestCollections object at 0x10b0c1190> def test_list(self): > assert [0, 1, 2] == [0, 1, 3] E assert [0, 1, 2] == [0, 1, 3] E At index 2 diff: 2 != 3 E Full diff: E - [0, 1, 3] E ? ^ E + [0, 1, 2] E ? ^ tests/test_assertions.py:33: AssertionError ___________________________________________________ TestCollections.test_list2 ___________________________________________________ self = <test_assertions.TestCollections object at 0x10b0d6c10> def test_list2(self): > assert [0, 1, 2] == [0, 1, [1, 2]] E assert [0, 1, 2] == [0, 1, [1, 2]] E At index 2 diff: 2 != [1, 2] E Full diff: E - [0, 1, [1, 2]] E ? ---- - E + [0, 1, 2] tests/test_assertions.py:36: AssertionError ____________________________________________________ TestCollections.test_set ____________________________________________________ self = <test_assertions.TestCollections object at 0x10b0c1a50> def test_set(self): > assert {0, 10, 11, 12} == {0, 20, 21} E AssertionError: assert {0, 10, 11, 12} == {0, 20, 21} E Extra items in the left set: E 10 E 11 E 12 E Extra items in the right set: E 20 E 21... E E ...Full output truncated (4 lines hidden), use '-vv' to show tests/test_assertions.py:39: AssertionError
可以看到对于嵌套的字典和列表,也能显示出不一致数据的具体位置。对于过长的数据,默认是会被truncated,可以通过-vv显示全部信息。
除了相等断言,还可以进行大于、小于、不等于、in/not in等类型的断言。
对于对象的断言,可以进行对象的类型断言、对象本身的断言。这里就不在一一举例,只要记住断言是使用assert语句,使用方法与在Python语言中的使用方法完全一致就可以了。
更多断言的例子,大家可以参考Pytest的官方文档:
https://docs.pytest.org/en/latest/example/reportingdemo.html
这里一共有44个断言的例子,非常全面,几乎涵盖了所有的相等断言的场景。
4、Pytest断言Excepiton
除了支持对代码正常运行的结果断言之外,Pytest也能够对Exception和Warnning进行断言,来断定某种条件下,一定会出现某种异常或者警告。在功能测试和集成测试中,这两类断言用的不多,这里简单介绍一下。
对于异常的断言,Pytest的语法是:with pytest.raises(异常类型),可以看下面的这个例子:
def test_zero_division(): with pytest.raises(ZeroDivisionError): 1 / 0
这个测试用例断言运算表达式1除以0会产生ZeroDivisionError异常。除了对异常类型进行断言,还可以对异常信息进行断言,比如:
def test_zero_division(): with pytest.raises(ZeroDivisionError) as excinfo: 1 / 0 assert 'division by zero' in str(excinfo.value)
这个测试用例,就断言了excinfo.value的内容中包含division by zero这个字符串,这在需要断言具体的异常信息时非常有用。
对于Warnning的断言,其实与Exception的断言的用法基本一致。这里就不介绍了,关于更多的Exception和Warnning的断言可以参考Pytest的官方文档https://docs.pytest.org/en/latest/assert.html#assertions-about-expected-exceptions
5、为断言添加自定义功能
通过前面的介绍,感觉Pytest的assert挺完美了,又简单又清晰。但是在实际的测试工作中,还会遇到一些实际问题,比如在断言时,最好【自动】添加一些日志,避免我们在测试代码中手动加入日志。还有,最好能将断言的信息,【自动】集成到一些测试报告中,比如Allure中(关于Allure报告大家可以看之前的文章《Pycharm+pytest+allure打造高逼格的测试报告》)。这样就能避免在每一个测试脚本中手动写很多重复的代码,从而让我们将更多的时间和精力放到编写测试用例上。
有了这样的想法,接下来看看如何实现。
Pytest中提供了一个Hook函数pytest_assertrepr_compare,这个函数会在测试脚本的assert语句执行时被调用。因此,可以实现这个函数,在函数中添加写日志和集成allure测试报告代码。
完整的代码如下所示:
# content of conftest.py def pytest_assertrepr_compare(config, op, left, right): left_name, right_name = inspect.stack()[7].code_context[0].lstrip().lstrip('assert').rstrip(' ').split(op) pytest_output = assertrepr_compare(config, op, left, right) logging.debug("{0} is\n {1}".format(left_name, left)) logging.debug("{0} is\n {1}".format(right_name, right)) with allure.step("校验结果"): allure.attach(str(left), left_name) allure.attach(str(right), right_name) return pytest_output
通过inspect获取调用栈信息,从中得到测试脚本中assert语句中op操作符两边的字符串名称,在日志和测试报告中会用到。接着执行assertrepr_compare输出错误详细信息,这些信息就是在执行断言失败时的输出内容,pytest_assertrepr_compare函数没有对其做任何修改。接着添加了debug日志输出和allure测试报告的内容,最后再将assert的错误信息返回给调用处。
实现了这个函数后,测试脚本不需要做任何修改,依然是直接使用assert进行断言。但是能够自动记录日志和生成allure测试报告了。
6、禁止Pytest的assert特性
如果不想要Pytest中的assert的效果,而是希望保持Python原生的assert效果,只需要在执行测试是指定一个选项:
--assert=plain
这样所有测试用例中的assert都变成了Python原生的assert效果了,如果只想某一个模块保持Python原生的assert效果,那么就在对应模块的docstring中添加PYTEST_DONT_REWRITE字符串就好了,也就是在py文件的最上面添加类似下面的docstring内容:
""" Disable rewriting for a specific module by adding the string: PYTEST_DONT_REWRITE """
不过,我想应该没有人会这么干,因为Pytest的assert还是更好用一些。
7、总结
本文对比了Python原生的assert与Pytest中的assert的区别,详细介绍了Pytest中assert的用法,并根据测试工作的实际需求,演示了如何通过pytest_assertrepr_compare这个Hook函数在断言时增加日志和报告输出。希望对你有帮助。
源自公众号 明说软件测试