Pytest | pytest-assume 插件

使用插件实现多重断言执行

Posted by Haauleon on June 26, 2023

背景

  使用 pytest 进行断言判断的时候,为了用例的精准性,经常会多个方面进行断言,比如如下:

1
2
3
4
5
# 断言1:断言响应的 http 的状态

# 断言2:断言响应返回的 code 值

# 断言3:断言响应返回的 json 中的 data 字段是否符合预期

  如果使用原生 python 的 assert,就会遇到一个断言失败则全部失败的情况。比如说,断言1 结果为 Failed,那么断言2 和断言3 都不会被执行。
  我们希望断言2 和断言3 继续执行,这样我们能获取更多的断言结果来判断出接口哪里出了问题,能够更好地进行问题定位,这时候该本文主角出现了:pytest-assume 插件。



pytest-assume简介

  一个可以允许 pytest 测试用例中执行多个失败的断言的插件(即上面断言1,断言2,断言3 都失败的情况下,三个断言都能被执行)。

项目:https://github.com/astraw38/pytest-assume

说明:(该插件源自 pytest-expect,并且做了一部分小的修改)
1、支持 showlocals(即 pytest 命令行的 -l 参数,显示执行过程中的局部变量)
2、可以全局使用,无需指定 fixtrue 装饰器。(即任意 test_xxx 函数中都能用)
3、对断言输出做了一些格式上的美化



pytest-assume安装

1
2
3
4
# 根据 python 版本,可选择 pip3 或者 pip
> sudo pip3(pip) install git+https://github.com/astraw38/pytest-assume.git
# 或者
> sudo pip3(pip) install pytest-assume



使用示例

1、一个对比原生 assert 和 pytest-assume 的测试用例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/env python3
#!coding:utf-8
import pytest

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
    assert x == y  # 如果这个断言失败,则后续都不会执行
    assert True
    assert False

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_pytest_assume(x, y):
    pytest.assume(x == y)  # 即使这个断言失败,后续仍旧执行
    pytest.assume(True)
    pytest.assume(False)

输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
===================================================================================== test session starts =====================================================================================
platform darwin -- Python 3.8.2, pytest-6.2.1, py-1.10.0, pluggy-0.13.1
rootdir: /Users/xxx/Desktop/pytest
plugins: assume-2.4.2, ordering-0.6

collected 6 items  # 这里执行了六个用例

test_demo.py FFF [100%]

========================================================================================== FAILURES ===========================================================================================
___________________________________________________________________________________ test_simple_assume[1-1] ___________________________________________________________________________________

x = 1, y = 1

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
    def test_simple_assume(x, y):
        assert x == y
        assert True
> assert False  # 前两个断言成功,第三个断言失败了
E assert False

test_demo.py:9: AssertionError
___________________________________________________________________________________ test_simple_assume[1-0] ___________________________________________________________________________________

x = 1, y = 0

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
> assert x == y  # 第一个断言失败了,后续断言不会被执行
E assert 1 == 0

test_demo.py:7: AssertionError
___________________________________________________________________________________ test_simple_assume[0-1] ___________________________________________________________________________________

x = 0, y = 1

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
> assert x == y  # 第一个断言失败了,后续断言不会被执行
E assert 0 == 1 

test_demo.py:7: AssertionError
___________________________________________________________________________________ test_pytest_assume[1-1] ___________________________________________________________________________________

tp = <class 'pytest_assume.plugin.FailedAssumption'>, value = None, tb = None

def reraise(tp, value, tb=None):
    try:
        if value is None:
            value = tp()
        if value.__traceback__ is not tb:
> raise value.with_traceback(tb)
E pytest_assume.plugin.FailedAssumption:
E 1 Failed Assumptions:
E
E test_demo.py:15: AssumptionFailure
E >> pytest.assume(False)
E AssertionError: assert False  # 前两个断言成功,第三个断言失败了

/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages/six.py:702: FailedAssumption
---------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------
F
___________________________________________________________________________________ test_pytest_assume[1-0] ___________________________________________________________________________________

tp = <class 'pytest_assume.plugin.FailedAssumption'>, value = None, tb = None

def reraise(tp, value, tb=None):
    try:
        if value is None:
            value = tp()
        if value.__traceback__ is not tb:
> raise value.with_traceback(tb)
E pytest_assume.plugin.FailedAssumption:
E 2 Failed Assumptions:
E
E test_demo.py:13: AssumptionFailure
E >> pytest.assume(x == y)  # 第一个断言失败,后续继续执行
E AssertionError: assert False
E
E test_demo.py:15: AssumptionFailure
E >> pytest.assume(False)
E AssertionError: assert False

/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages/six.py:702: FailedAssumption
---------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------
F
___________________________________________________________________________________ test_pytest_assume[0-1] ___________________________________________________________________________________

tp = <class 'pytest_assume.plugin.FailedAssumption'>, value = None, tb = None

def reraise(tp, value, tb=None):
    try:
        if value is None:
            value = tp()
        if value.__traceback__ is not tb:
> raise value.with_traceback(tb)
E pytest_assume.plugin.FailedAssumption:
E 2 Failed Assumptions:
E
E test_demo.py:13: AssumptionFailure
E >> pytest.assume(x == y)  # 第一个断言失败,后续继续执行
E AssertionError: assert False
E
E test_demo.py:15: AssumptionFailure
E >> pytest.assume(False)
E AssertionError: assert False

/Library/Developer/CommandLineTools/Library/Frameworks/Python3.framework/Versions/3.8/lib/python3.8/site-packages/six.py:702: FailedAssumption
---------------------------------------------------------------------------------- Captured stdout teardown -----------------------------------------------------------------------------------
F
=================================================================================== short test summary info ===================================================================================
FAILED test_demo.py::test_simple_assume[1-1] - assert False
FAILED test_demo.py::test_simple_assume[1-0] - assert 1 == 0
FAILED test_demo.py::test_simple_assume[0-1] - assert 0 == 1
FAILED test_demo.py::test_pytest_assume[1-1] - pytest_assume.plugin.FailedAssumption:
FAILED test_demo.py::test_pytest_assume[1-0] - pytest_assume.plugin.FailedAssumption:
FAILED test_demo.py::test_pytest_assume[0-1] - pytest_assume.plugin.FailedAssumption:
====================================================================================== 6 failed in 0.19s ======================================================================================

这里我们可以看出二者的区别了,执行差异如下:

断言类型 1, 1 1, 0 0, 1 结论
assert 断言3 失败 断言1 失败,
断言2 和断言3 不执行
断言1 失败,断言2 和断言3 不执行 assert 遇到断言失败则停下
pytest.assume 断言3 失败 断言1 失败,
断言2 和断言3 继续执行
断言1 失败,断言2 和断言3 继续执行 pytest.assume 无论断言结果失败与否,全部执行



2、 通过上下文管理器 with 使用 pytest-assume

1
2
3
4
5
6
7
8
9
10
11
12
#!/usr/bin/env python3
#!coding:utf-8
import pytest
# from pytest import assume
from pytest_assume.plugin import assume

@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
    #使用上下文管理器的好处是不用显示去try和finally捕获异常,建议使用这种写法,简洁有效。
    with assume: assert x == y
    with assume: assert True
    with assume: assert False

主要注意的是,如果上下文管理器里面包含多个断言,则只有第一个会被执行,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#!/usr/bin/env python3
#!coding:utf-8
import pytest
# from pytest import assume
from pytest_assume.plugin import assume
    
@pytest.mark.parametrize(('x', 'y'), [(1, 1), (1, 0), (0, 1)])
def test_simple_assume(x, y):
    #使用上下文管理器的好处是不用显示去try和finally捕获异常,建议使用这种写法,简洁有效。
    with assume: 
        #只有第一个断言会被执行!
        assert x == y
        assert True
        assert False




相关链接:
pytest-assume插件(全网最详细解释):多重断言执行
pytest assume无法导入:解决ImportError: cannot import name ‘assume‘ from ‘pytest‘问题