Pytest 测试框架

🍰 PyTest是Python的一种单元测试框架,与Python自带的UnitTest测试框架类似,但使用比UnitTest更简洁,效率更高。

1 规则说明

  • 规则说明
    • PyTest是Python2默认自带的,在Python3中则是独立的,需使用pip install pytest进行安装。
    • 运行规则
      • 查找当前目录及子目录中test_*.py*_test.py文件。
      • 找到所有的文件后,执行文件中以test开头的所有函数。
    • 用例设计规则
      • 测试文件以test_开头,或以_test结尾,测试类以Test开头,并且不能带有init方法。
      • 测试函数和方法以test_开头,断言用assert,所有的package包必须带__init__.py文件。
1
2
3
4
5
pip install pytest                                       # 安装PyTest
pip show pytest # 查看PyTest版本的详细信息
pytest --version # 简单查看PyTest的安装版本
pytest -h # PyTest查看帮助
pytest --help # PyTest查看帮助

1-1 用例执行规则

  • 用例执行规则
    • 执行某个目录下所有的用例:pytest FolderName/
    • 执行某个文件中的所有用例:pytest FileName.py
    • 遇到错误时,停止正在执行的测试用例:pytest -x FileName.py
    • 用例错误个数达到指定数量时,停止测试:pytest --maxfail=num
    • 按照关键字进行模糊匹配,匹配名称不区分大小写:pytest -k "xxx"
    • 简单结果:pytest -q FileName.py--quiet decrease verbosity
    • 按节点运行
      • 每个测试用例都分配了唯一的nodeid,nodeid由模块名和说明符组成。
      • 模块名和说明符以::间隔,说明符可以包含类名、函数名和标记参数。
    • 标记表达式运行用@pytest.mark.slow修饰器修饰的所有用例:pytest -m slow
1
2
3
4
5
6
7
8
9
10
11
12
pytest                                                  # 执行PyTest用例的命令,推荐使用
py.test # 执行该文件夹下所有符合条件的用例
python -m pytest

pytest -k "ClassName" # 运行ClassName类下的所有方法
pytest -k "MethodName" # 运行所有名为MethodName的方法
pytest -k "ClassName and not MethodName" # 运行ClassName类下的除MethodName外的所有方法

pytest FileName.py::FunctionName # 函数名
pytest FileName.py::ClassName::FunctionName # 类名+函数名
pytest FileName.py::ClassName::FunctionName[num1-num2] # 类名+函数名+标记参数
pytest FileName.py -vv # FileName.py文件下的所有用例节点执行结果

1-2 PyCharm运行方式

  • PyCharm运行方式
    • 默认以Autodetect形式运行,可以通过修改PyCharm设置来改变运行方式。
    • File—>Setting—>Tools—>Python Integrated Tools—>Default test runner。
    • PyTest兼容UnitTest,用UnitTest框架编写的测试用例也能用PyTest框架运行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pytest                                           # test_node.py

def test_one():
pass

class TestNode:
def test_one(self):
pass

@pytest.mark.parametrize("x, y",[(1, 3), (3, 5)])
def test_two(self, x, y):
assert x + 2 == y

# pytest test_node.py::test_one # 函数名
# pytest test_node.py::TestNode::test_one # 类名+函数名
# pytest test_node.py::TestNode::test_two[1-3] # 类名+函数名+标记参数
# pytest test_node.py::TestNode::test_two[3-5] # 类名+函数名+标记参数
# pytest test_node.py -vv # 该文件下的所有用例节点执行结果

2 跳过用例

  • 跳过用例
    • 装饰器@pytest.mark.skip():标记测试用例为跳过状态,并提供跳过原因。
    • 装饰器@pytest.mark.skipif():当指定的条件满足时,即为True,跳过测试用例并提供跳过原因。
    • 装饰器@pytest.mark.xfail():标记测试用例为预计失败,实际运行时会被忽略,不影响测试结果。
    • 函数pytest.skip():在测试用例执行期间,强制跳过当前测试用例并提供跳过原因,类似于break跳出循环。
    • 函数pytest.xfail():标记已知存在问题但尚未修复的用例,依赖于被标记成xfail用例的其他用例也会跳过。
    • 自定义跳过标识
      • pytest.mark.skip()pytest.mark.skipif()赋值给一个自定义标识变量,不同模块之间进行共享。
      • 若有多个模块的用例需要用到同个标识变量,可单独文件管理通用的标识变量,适用于整个测试用例集。
    • 函数pytest.importorskip():某用例依赖于特定模块,但当前环境中该模块不可导入时使用该函数跳过用例。

2-1 @pytest.mark.skip()

  • @pytest.mark.skip()
    • 用在跳过单个用例(函数用例)、类用例、类方法用例上,如果加在类用例上,类里面的所有测试用例都不会被执行。
    • 可选参数reason,代表跳过原因,会在执行结果中打印,可选参数condition,用于定义在特定条件下跳过测试用例。
    • pytestmark=pytest.mark.skip(),pytestmark是pytest中一个特殊的不可更改变量名,用于设置模块级别的标记。
    • 如果想在模块级别跳过测试用例,但不使用pytestmark,则可以使用其他变量名,注意需要使用装饰器来进行标记。

(1) test_mark_skip.py

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
import pytest                                           # test_mark_skip.py


@pytest.mark.skip(reason="跳过单个测试用例")
def test_a():
print("---test case a---")


@pytest.mark.skip(reason="跳过单个类的用例")
class TestCaseA:
def test_b(self):
print("---test case b---")

def test_c(self):
print("---test case c---")


class TestCaseB:
@pytest.mark.skip(reason="跳过类的方法用例")
def test_d(self):
print("---test case d---")

def test_e(self):
print("---test case e---")


if __name__ == "__main__":
pytest.main(["-s", "test_mark_skip.py"])

"""
pytest test_mark_skip.py # test_mark_skip.py ssss.
============================= test session starts =============================
collecting ... collected 5 items

test_mark_skip.py::test_a [ 20%]
SKIPPED (跳过单个测试用例)
Skipped: 跳过单个测试用例

test_mark_skip.py::TestCaseA::test_b [ 40%]
SKIPPED (跳过单个类的用例)
Skipped: 跳过单个类的用例

test_mark_skip.py::TestCaseA::test_c [ 60%]
SKIPPED (跳过单个类的用例)
Skipped: 跳过单个类的用例

test_mark_skip.py::TestCaseB::test_d [ 80%]
SKIPPED (跳过类的方法用例)
Skipped: 跳过类的方法用例

test_mark_skip.py::TestCaseB::test_e
PASSED [100%]---test case e---
======================== 1 passed, 4 skipped in 0.16s =========================
"""

(2) test_skip_module.py*

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
import pytest                                           # test_skip_module.py

pytestmark = pytest.mark.skip(reason="跳过当前模块")
# skip_module = pytest.mark.skip(reason="跳过当前模块")


def test_a(): # 奇怪,pytestmark这里也跳过了
print("---use case a---") # skip_module只跳过TestCase


# @skip_module
class TestCase:
def test_b(self):
print("---use case b---")

def test_c(self):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_skip_module.py"])

"""
pytest test_skip_module.py # test_skip_module.py sss
============================= test session starts =============================
collecting ... collected 3 items

test_skip_module.py::test_a [ 33%]
SKIPPED (跳过当前模块)
Skipped: 跳过当前模块

test_skip_module.py::TestCase::test_b [ 66%]
SKIPPED (跳过当前模块)
Skipped: 跳过当前模块

test_skip_module.py::TestCase::test_c [100%]
SKIPPED (跳过当前模块)
Skipped: 跳过当前模块
============================= 3 skipped in 0.05s ==============================
"""

2-2 @pytest.mark.skipif()

  • @pytest.mark.skipif()
    • 可选参数reason,用于添加关于跳过测试用例的原因说明。
    • 可选参数condition,定义特定条件下是否跳过用例的表达式。
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
import sys
import pytest # test_mark_skipif.py


@pytest.mark.skipif(sys.version_info < (3, 10), reason="python版本小于3.10")
def test_a():
print("---use case a---")


@pytest.mark.skipif(condition=sys.version_info < (3, 10), reason="python版本小于3.10")
class TestCaseA:
def test_b(self):
print("---use case b---")

def test_c(self, ):
print("---use case c---")


class TestCaseB:
@pytest.mark.skipif(condition=sys.version_info < (3, 10), reason="python版本小于3.10")
def test_d(self):
print("---use case d---")

def test_e(self, ):
print("---use case e---")


if __name__ == "__main__":
pytest.main(["-s", "test_mark_skipif.py"])

""" !!!这里用的Python3.9版本
pytest test_mark_skipif.py # test_mark_skipif.py ssss.
============================= test session starts =============================
collecting ... collected 5 items

test_mark_skipif.py::test_a [ 20%]
SKIPPED (python版本小于3.10)
Skipped: python版本小于3.10

test_mark_skipif.py::TestCaseA::test_b [ 40%]
SKIPPED (python版本小于3.10)
Skipped: python版本小于3.10

test_mark_skipif.py::TestCaseA::test_c [ 60%]
SKIPPED (python版本小于3.10)
Skipped: python版本小于3.10

test_mark_skipif.py::TestCaseB::test_d [ 80%]
SKIPPED (python版本小于3.10)
Skipped: python版本小于3.10

test_mark_skipif.py::TestCaseB::test_e
PASSED [100%]---use case e---
======================== 1 passed, 4 skipped in 0.06s =========================
"""

2-3 @pytest.mark.xfail()

  • @pytest.mark.xfail()
    • 标记测试用例为预期失败状态,预期失败即该用例的目标是失败的。
    • 参数run用于控制在特定的条件下,是否执行标记为xfail的测试用例。
    • 参数reason用于提供预期失败的原因说明,参数raises则指定预期抛出的异常类型。
    • 参数strict用于控制测试用例在预期失败时,是否以失败的标记显示在测试报告中。
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
import pytest  # test_mark_xfail.py


@pytest.mark.xfail(reason="测试用例预期失败")
def test_a():
assert 2 + 2 == 5


@pytest.mark.xfail(raises=ZeroDivisionError)
def test_b():
assert 10 / 0 == 5


@pytest.mark.xfail(run=False)
def test_c():
assert 2 + 1 == 5


@pytest.mark.xfail(strict=True)
def test_d():
assert False


if __name__ == "__main__":
pytest.main(["-s", "test_mark_xfail.py"])

"""
pytest test_mark_xfail.py # test_mark_xfail.py xxxx
============================= test session starts ============================
collecting ... collected 4 items

test_mark_xfail.py::test_a [ 25%]
XFAIL (测试用例预期失败)
@pytest.mark.xfail(reason="测试用例预期失败")
def test_a():
> assert 2 + 2 == 5
E assert 4 == 5
E +4
E -5

test_mark_xfail.py:6: AssertionError

test_mark_xfail.py::test_b
XFAIL [ 50%]
@pytest.mark.xfail(raises=ZeroDivisionError)
def test_b():
> assert 10 / 0 == 5
E ZeroDivisionError: division by zero

test_mark_xfail.py:11: ZeroDivisionError

test_mark_xfail.py::test_c
XFAIL ([NOTRUN] ) [ 75%]
cls = <class '_pytest.runner.CallInfo'>
func = <function call_runtest_hook.<locals>.<lambda> at 0x0000015C2E6DA5E0>
when = 'setup'
reraise = (<class '_pytest.outcomes.Exit'>, <class 'KeyboardInterrupt'>)

@classmethod
def from_call(
cls,
func: "Callable[[], TResult]",
when: "Literal['collect', 'setup', 'call', 'teardown']",
reraise: Optional[
Union[Type[BaseException], Tuple[Type[BaseException], ...]]
] = None,
) -> "CallInfo[TResult]":
excinfo = None
start = timing.time()
precise_start = timing.perf_counter()
try:
> result: Optional[TResult] = func()

D:\A3\lib\site-packages\_pytest\runner.py:311:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
D:\A3\lib\site-packages\_pytest\runner.py:255: in <lambda>
lambda: ihook(item=item, **kwds), when=when, reraise=reraise
D:\A3\lib\site-packages\pluggy\hooks.py:286: in __call__
return self._hookexec(self, self.get_hookimpls(), kwargs)
D:\A3\lib\site-packages\pluggy\manager.py:93: in _hookexec
return self._inner_hookexec(hook, methods, kwargs)
D:\A3\lib\site-packages\pluggy\manager.py:84: in <lambda>
self._inner_hookexec = lambda hook, methods, kwargs: hook.multicall(
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _

item = <Function test_c>

@hookimpl(tryfirst=True)
def pytest_runtest_setup(item: Item) -> None:
skipped = evaluate_skip_marks(item)
item._store[skipped_by_mark_key] = skipped is not None
if skipped:
skip(skipped.reason)

item._store[xfailed_key] = xfailed = evaluate_xfail_marks(item)
if xfailed and not item.config.option.runxfail and not xfailed.run:
> xfail("[NOTRUN] " + xfailed.reason)
E _pytest.outcomes.XFailed: [NOTRUN]

D:\A3\lib\site-packages\_pytest\skipping.py:249: XFailed

test_mark_xfail.py::test_d
XFAIL [100%]
@pytest.mark.xfail(strict=True)
def test_d():
> assert False
E assert False

test_mark_xfail.py:21: AssertionError
============================= 4 xfailed in 0.47s =============================
"""

2-4 pytest.skip()

  • pytest.skip()
    • 用于跳过特定测试用例的执行,可以在测试函数、方法或类上使用。
    • 通常用于某功能尚未实现,但已编写了测试用例,暂时跳过这些用例的情况。
    • 或者测试用例依赖于某些条件,但这些条件当前不满足,因此跳过这些用例。

(1) test_skip_msg.py

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
import pytest                                           # test_skip_msg.py


def test_case():
n = 0
while True:
print(f"---use case {n} execution---")
n += 1
if n == 5: # pytest.skip(msg="")
pytest.skip("---use case 5 exit---")


if __name__ == "__main__":
pytest.main(["-s", "test_skip_msg.py"])

"""
pytest test_skip_msg.py # test_skip_msg.py s
============================= test session starts =============================
collecting ... collected 1 item

test_skip_msg.py::test_case
SKIPPED (---use case 5 exit---) [100%]---use case 0 execution---
---use case 1 execution---
---use case 2 execution---
---use case 3 execution---
---use case 4 execution---

Skipped: ---use case 5 exit---
============================= 1 skipped in 0.15s ==============================
"""

(2) test_skip_allow.py

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
import sys
import pytest # test_skip_allow.py

if not sys.platform.startswith("darwin"): # allow_module_level=True,设置跳过整个模块
pytest.skip("测试用例只在Mac平台上执行", allow_module_level=True)


def test_a():
print("---use case a---")


class TestCase:
def test_b(self):
print("---use case b---")

def test_c(self):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_skip_allow.py"])

""" !!!win是Windows平台,darwin是Mac平台,Linux是Linux平台
pytest test_skip_allow.py # collected 0 items / 1 skipped
============================= test session starts =============================
collecting ...
Skipped: 测试用例只在Mac平台上执行
collected 0 items / 1 skipped
============================= 1 skipped in 0.04s ==============================
"""

2-5 pytest.xfail()

  • pytest.xfail()
    • 用于标记测试用例为预期失败状态,预期失败即该测试用例的目标是失败的。
    • 参数reason提供预期失败的原因说明,参数raises则指定预期抛出的异常类型。
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
import pytest                                           # test_xfail.py


@pytest.xfail(reason="测试用例预期失败")
def test_a():
assert 2 + 2 == 5


@pytest.xfail(raises=ZeroDivisionError)
def test_b():
assert 10 / 0 == 5


if __name__ == "__main__":
pytest.main(["-s", "test_xfail.py"])

"""
pytest test_xfail.py # collected 0 items / 1 error
============================= test session starts =============================
collecting ...
test_xfail.py:None (test_xfail.py)
test_xfail.py:4: in <module>
@pytest.xfail(reason="测试用例预期失败")
E _pytest.outcomes.XFailed: 测试用例预期失败
collected 0 items / 1 error

!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!
============================== 1 error in 0.34s ===============================
"""

2-6 自定义跳过标识

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
import sys
import pytest # test_skip_tag.py

# mark_version = pytest.mark.skip(reason="不在Win下执行")
mark_version = pytest.mark.skipif(sys.platform != "win", reason="不在Win下执行")


@mark_version
def test_a():
print("---use case a---")


class TestCase:
def test_b(self):
print("---use case b---")

@mark_version
def test_c(self, ):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_skip_tag.py"])

"""
pytest test_skip_tag.py # test_skip_tag.py s.s
============================= test session starts =============================
collecting ... collected 3 items

test_skip_tag.py::test_a [ 33%]
SKIPPED (不在Win下执行)
Skipped: 不在Win下执行

test_skip_tag.py::TestCase::test_b [ 66%]---use case b---
PASSED

test_skip_tag.py::TestCase::test_c [100%]
SKIPPED (不在Win下执行)
Skipped: 不在Win下执行
======================== 1 passed, 2 skipped in 0.05s =========================
"""

2-7 pytest.importorskip()

  • pytest.importorskip()
    • pytest.importorskip(modname: str, minversion: Optional[str] = None, reason: Optional[str] = None)
    • 参数列表:modname-模块名、minversion-版本号、reason-跳过原因,模块名必选,版本号和跳过原因都是可选的。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pytest                                           # test_importorskip.py

sys = pytest.importorskip("sys", minversion="1.0")


@sys
def test_case():
print("---test cases---")


if __name__ == "__main__":
pytest.main(["-s", "test_importorskip.py"])

"""
pytest test_importorskip.py # collected 0 items / 1 skipped
============================= test session starts =============================
collecting ...
Skipped: module 'sys' has __version__ None, required is: '1.0'
collected 0 items / 1 skipped
============================= 1 skipped in 0.27s ==============================
"""

3 assert断言

  • assert断言
    • 即实际结果和期望结果的比对,符合预期就pass,不符合则failed。
    • PyTest允许使用标准的Python断言assert来验证测试中的期望和值。
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
def f():                                                # test_assert.py
return 3


def test_function():
assert f() == 5


"""
============================= test session starts =============================
collecting ... collected 1 item

test_assert.py::test_function FAILED [100%]
test_assert.py:4 (test_function)
3 != 5

Expected :5
Actual :3
<Click to see difference>

def test_function():
> assert f() == 5
E assert 3 == 5
E +3
E -5

test_assert.py:6: AssertionError
============================== 1 failed in 0.33s ==============================
"""

3-1 异常信息

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
def f():                                                # test_exception_message.py
return 3


def test_function():
x = f()
assert x % 2 == 0, "确定是否为偶数,并且当前值为%s" % x


"""
============================= test session starts =============================
collecting ... collected 1 item

test_exception_message.py::test_function FAILED [100%]
test_exception_message.py:4 (test_function)
1 != 0

Expected :0
Actual :1
<Click to see difference>

def test_function():
x = f()
> assert x % 2 == 0, "确定是否为偶数,并且当前值为%s" % x
E AssertionError: 确定是否为偶数,并且当前值为3
E assert 1 == 0
E +1
E -0

test_exception_message.py:7: AssertionError
============================== 1 failed in 0.45s ==============================
"""

3-2 异常断言

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import pytest                                           # test_anomaly_assertion.py


def test_zero_division():
with pytest.raises(ZeroDivisionError): # 使用pytest.raises作为上下文管理器
1 / 0


"""
============================= test session starts =============================
collecting ... collected 1 item

test_anomaly_assertion.py::test_zero_division PASSED [100%]
============================== 1 passed in 0.14s ==============================
"""
  • 异常类型
    • 要判断抛出的异常是否预期,例如1/0抛出的异常为:ZeroDivisionError: division by zero
    • 断言该异常,通常是断言异常的type和value值,type是ZeroDivisionError,value是division by zero。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import pytest                                           # test_exception_type.py


def test_zero_division(): # 断言异常
with pytest.raises(ZeroDivisionError) as excinfo: # 使用pytest.raises作为上下文管理器
1 / 0

assert excinfo.type == ZeroDivisionError # 断言异常type值
assert "division by zero" in str(excinfo.value) # 断言异常value值


"""
============================= test session starts =============================
collecting ... collected 1 item

test_exception_type.py::test_zero_division PASSED [100%]
============================== 1 passed in 0.12s ==============================
"""

3-3 常用断言

  • 常用断言
    • assert xx:判断xx为真。
    • assert not xx:判断xx不为真。
    • assert a in b:判断b包含a。
    • assert a == b:判断a等于b。
    • assert a != b:判断a不等于b。
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
import pytest                                           # test_common_asserts.py


def is_true(a):
if a > 0:
return True
else:
return False


def test_01():
a = 5
b = -1
assert is_true(a) # 断言a为真
assert not is_true(b) # 断言b不为真


def test_02():
a = "hello"
b = "hello world"
assert a in b # 断言b包含a


def test_03():
a = "Yoyo"
b = "Yoyo"
assert a == b # 断言相等


def test_04():
a = 5
b = 6
assert a != b # 断言不相等


if __name__ == "__main__":
pytest.main(["-s", "test_common_asserts.py"])

"""
============================= test session starts =============================
collecting ... collected 4 items

test_common_asserts.py::test_01 PASSED [ 25%]
test_common_asserts.py::test_02 PASSED [ 50%]
test_common_asserts.py::test_03 PASSED [ 75%]
test_common_asserts.py::test_04 PASSED [100%]
============================== 4 passed in 0.14s ==============================
"""

4 用例运行级别

  • 用例运行级别
    • 模块级:setup_module、teardown_module,在所有用例前后只执行一次。
    • 函数级:setup_function、teardown_function,在所有用例前后都执行一次。
    • 类级别:setup_class、teardown_class,在类中的所有用例前后只执行一次。
    • 类方法级:setup_method、teardown_method,在类中的所有用例前后都执行一次。
    • 类方法细化级:setup、teardown,运行规则与类方法级相同,在每个用例前后都执行一次。

4-1 模块级

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
import pytest                                           # test_module.py


def setup_module(): # 模块级
print("---setup_module: 在所有用例前后只执行一次---")


def teardown_module():
print("---teardown_module: 在所有用例前后只执行一次---")


def test_one():
print("---执行中---test_one()")
x = "this"
assert "h" in x


def test_two():
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_module.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

---setup_module: 在所有用例前后只执行一次---
test_module.py::test_one PASSED [ 50%]---执行中---test_one()

test_module.py::test_two PASSED [100%]---执行中---test_two()
---teardown_module: 在所有用例前后只执行一次---
============================== 2 passed in 0.14s ==============================
"""

4-2 函数级

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
import pytest                                           # test_function.py


def setup_function(): # 函数级
print("---setup_function: 在所有用例前后都执行一次---")


def teardown_function():
print("---teardown_function: 在所有用例前后都执行一次---")


def test_one():
print("---执行中---test_one()")
x = "this"
assert "h" in x


def test_two():
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_function.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

---setup_function: 在所有用例前后都执行一次---
test_function.py::test_one PASSED [ 50%]---执行中---test_one()
---teardown_function: 在所有用例前后都执行一次---

---setup_function: 在所有用例前后都执行一次---
test_function.py::test_two PASSED [100%]---执行中---test_two()
---teardown_function: 在所有用例前后都执行一次---
============================== 2 passed in 0.04s ==============================
"""

4-3 类级别

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
import pytest                                           # test_class.py


class TestCase: # 类级别
def setup_class(self):
print("---setup_class: 在类中的所有用例前后只执行一次---")

def teardown_class(self):
print("---teardown_class: 在类中的所有用例前后只执行一次---")

def test_one(self):
print("---执行中---test_one()")
x = "this"
assert "h" in x

def test_two(self):
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_class.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

---setup_class: 在类中的所有用例前后只执行一次---
test_class.py::TestCase::test_one PASSED [ 50%]---执行中---test_one()

test_class.py::TestCase::test_two PASSED [100%]---执行中---test_two()
---teardown_class: 在类中的所有用例前后只执行一次---
============================== 2 passed in 0.02s ==============================
"""

4-4 类方法级

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
import pytest                                           # test_method.py


class TestCase: # 类方法级
def setup_method(self):
print("---setup_method: 在类中的所有用例前后都执行一次---")

def teardown_method(self):
print("---teardown_method: 在类中的所有用例前后都执行一次---")

def test_one(self):
print("---执行中---test_one()")
x = "this"
assert "h" in x

def test_two(self):
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_method.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

---setup_method: 在类中的所有用例前后都执行一次---
test_method.py::TestCase::test_one PASSED [ 50%]---执行中---test_one()
---teardown_method: 在类中的所有用例前后都执行一次---

---setup_method: 在类中的所有用例前后都执行一次---
test_method.py::TestCase::test_two PASSED [100%]---执行中---test_two()
---teardown_method: 在类中的所有用例前后都执行一次---
============================== 2 passed in 0.12s ==============================
"""

4-5 类方法细化级

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
import pytest                                           # test_fixclass.py


class TestCase: # 类方法细化级
def setup(self):
print("---setup: 在每个用例前后都执行一次---")

def teardown(self):
print("---teardown: 在每个用例前后都执行一次---")

def test_one(self):
print("---执行中---test_one()")
x = "this"
assert "h" in x

def test_two(self):
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_fixclass.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

---setup: 在每个用例前后都执行一次---
test_fixclass.py::TestCase::test_one PASSED [ 50%]---执行中---test_one()
---teardown: 在每个用例前后都执行一次---

---setup: 在每个用例前后都执行一次---
test_fixclass.py::TestCase::test_two PASSED [100%]---执行中---test_two()
---teardown: 在每个用例前后都执行一次---
============================== 2 passed in 0.14s ==============================
"""

4-6 类和方法混合

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
import pytest                                           # test_mixclass.py


class TestCase:
def setup_class(self): # 类级
print("---setup_class: 在类中的所有用例前后只执行一次---")

def teardown_class(self):
print("---teardown_class: 在类中的所有用例前后只执行一次---")

def setup_method(self): # 方法级
print("---setup_method: 在类中的所有用例前后都执行一次---")

def teardown_method(self):
print("---teardown_method: 在类中的所有用例前后都执行一次---")

def setup(self): # 类方法细化级
print("---setup: 在每个用例前后都执行一次---")

def teardown(self):
print("---teardown: 在每个用例前后都执行一次---")

def test_one(self):
print("---执行中---test_one()")
x = "this"
assert "h" in x

def test_two(self):
print("---执行中---test_two()")
x = "hello"
assert "o" in x


if __name__ == "__main__":
pytest.main(["-s", "test_mixclass.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items
---setup_class: 在类中的所有用例前后只执行一次---
---setup_method: 在类中的所有用例前后都执行一次---
---setup: 在每个用例前后都执行一次---
test_mixclass.py::TestCase::test_one PASSED [ 50%]---执行中---test_one()
---teardown: 在每个用例前后都执行一次---
---teardown_method: 在类中的所有用例前后都执行一次---

---setup_method: 在类中的所有用例前后都执行一次---
---setup: 在每个用例前后都执行一次---
test_mixclass.py::TestCase::test_two PASSED [100%]---执行中---test_two()
---teardown: 在每个用例前后都执行一次---
---teardown_method: 在类中的所有用例前后都执行一次---
---teardown_class: 在类中的所有用例前后只执行一次---
============================== 2 passed in 0.05s ==============================
"""

4-7 模块和类混合

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
import pytest                                           # test_mixmodule.py


def setup_module(): # 模块级
print("---setup_module: 在所有用例前后只执行一次---")


def teardown_module():
print("---teardown_module: 在所有用例前后只执行一次---")


def setup_function(): # 函数级
print("---setup_function: 在所有用例前后都执行一次---")


def teardown_function():
print("---teardown_function: 在所有用例前后都执行一次---")


def test_a():
print("---执行中---test_a()")
x = "this"
assert "h" in x


def test_b():
print("---执行中---test_b()")
x = "hello"
assert "o" in x


class TestCase: # 类级别
def setup_class(self):
print("---setup_class: 在类中的所有用例前后只执行一次---")

def teardown_class(self):
print("---teardown_class: 在类中的所有用例前后只执行一次---")

def test_c(self):
print("---执行中---test_c()")
x = "class"
assert "c" in x

def test_d(self):
print("---执行中---test_d()")
x = "module"
assert "m" in x


if __name__ == "__main__":
pytest.main(["-s", "test_mixmodule.py"])

""" !!!注意:模块级优先权最大,函数级和类级别互不干涉。
============================= test session starts =============================
collecting ... collected 4 items

---setup_module: 在所有用例前后只执行一次---
---setup_function: 在所有用例前后都执行一次---
test_mixmodule.py::test_a PASSED [ 25%]---执行中---test_a()
---teardown_function: 在所有用例前后都执行一次---

---setup_function: 在所有用例前后都执行一次---
test_mixmodule.py::test_b PASSED [ 50%]---执行中---test_b()
---teardown_function: 在所有用例前后都执行一次---

---setup_class: 在类中的所有用例前后只执行一次---
test_mixmodule.py::TestCase::test_c PASSED [ 75%]---执行中---test_c()
test_mixmodule.py::TestCase::test_d PASSED [100%]---执行中---test_d()
---teardown_class: 在类中的所有用例前后只执行一次---

---teardown_module: 在所有用例前后只执行一次---
============================== 4 passed in 0.14s ==============================
"""

5 fixture预置条件

  • fixture预置条件
    • 相比用例运行级别的setup和teardown,fixture的命名方式更为灵活。
    • @pytest.fixture(scope="function", params="None", autouse=False, ids=None, name=None)
      • scope:类似于作用域,默认为function,还可以是class、module、session、package。
      • autouse:默认为False,用例手动调用该fixture,若为True则作用域内的用例自动调用。
      • name:默认的装饰器名称,同一模块的fixture相互调用时可以使用不同的name进行命名。
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
import pytest                                           # test_fixture.py


@pytest.fixture() # 不带参数时默认scope="function"
def login():
print("请输入账号和密码进行登录")


def test_a(login):
print("---use case a---")


def test_b(): # 不传参login
print("---use case b---")


def test_c(login):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_fixture.py"])

""" !!!实现场景:用例a需要登录,用例b不需要登录,用例c需要登录。
============================= test session starts =============================
collecting ... collected 3 items

test_fixture.py::test_a 请输入账号和密码进行登录
PASSED [ 33%]---use case a---

test_fixture.py::test_b
PASSED [ 66%]---use case b---

test_fixture.py::test_c 请输入账号和密码进行登录
PASSED [100%]---use case c---
============================== 3 passed in 0.04s ==============================
"""

5-1 conftest.py

  • conftest.py
    • 配置文件里可以实现数据共享(多个.py文件的预置操作),不用import,PyTest就能自动查找配置。
    • 注意:conftest.py配置脚本名称固定,与运行的用例要在同个package下且有__init__.py文件。

(1) conftest.py

1
2
3
4
5
6
import pytest                                            # conftest.py


@pytest.fixture()
def login():
print("请输入账号和密码进行登录")

(2) test_conftest.py

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
import pytest                                           # test_conftest.py


def test_a(login):
print("---use case a---")


def test_b(): # 不传参login
print("---use case b---")


def test_c(login):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_conftest.py"])

""" !单独运行test_conftest.py时可以调用到conftest.py配置文件中的login()方法。
============================= test session starts =============================
collecting ... collected 3 items

test_conftest.py::test_a 请输入账号和密码进行登录
PASSED [ 33%]---use case a---

test_conftest.py::test_b
PASSED [ 66%]---use case b---

test_conftest.py::test_c 请输入账号和密码进行登录
PASSED [100%]---use case c---
============================== 3 passed in 0.14s ==============================
"""

5-2 scope实现setup

  • scope实现setup
    • fixture的参数scope="module"可以实现多个.py跨文件共享前置。
    • module作用于整个.py文件,用例调用时,参数写上函数名称即可。
    • scope="module"可以理解为在测试用例中加上前置条件,类似setup。

(1) 用例全局调用

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
import pytest                                           # test_scope_whole.py


@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")


def test_a(open):
print("---test case a: 搜索 pytest---")


def test_b(open):
print("---test case b: 搜索 pytest---")


def test_c(open):
print("---test case c: 搜索 pytest---")


if __name__ == "__main__":
pytest.main(["-s", "test_scope_whole.py"])

""" !!!三个用例都调用了open函数,但只在用例a处执行一次。
============================= test session starts =============================
collecting ... collected 3 items

test_scope_whole.py::test_a 打开浏览器并打开百度首页
PASSED [ 33%]---test case a: 搜索 pytest---

test_scope_whole.py::test_b
PASSED [ 66%]---test case b: 搜索 pytest---

test_scope_whole.py::test_c
PASSED [100%]---test case c: 搜索 pytest---
============================== 3 passed in 0.04s ==============================
"""

(2) 部分用例调用

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
import pytest                                           # test_scope_part.py


@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")


def test_a():
print("---test case a: 搜索 pytest---")


def test_b(open):
print("---test case b: 搜索 pytest---")


def test_c():
print("---test case c: 搜索 pytest---")


if __name__ == "__main__":
pytest.main(["-s", "test_scope_part.py"])

""" !!!用例a和用例c不调用open函数,用例b调用,只在用例b处执行一次。
============================= test session starts =============================
collecting ... collected 3 items

test_scope_part.py::test_a
PASSED [ 33%]---test case a: 搜索 pytest---

test_scope_part.py::test_b 打开浏览器并打开百度首页
PASSED [ 66%]---test case b: 搜索 pytest---

test_scope_part.py::test_c
PASSED [100%]---test case c: 搜索 pytest---
============================== 3 passed in 0.14s ==============================
"""

5-3 yield实现teardown

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
import pytest                                           # test_yield.py


@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")

yield
print("执行teardown")
print("关闭浏览器")


def test_a(open):
print("---test case a: 搜索 pytest---")


def test_b(open):
print("---test case b: 搜索 pytest---")


def test_c(open):
print("---test case c: 搜索 pytest---")


if __name__ == "__main__":
pytest.main(["-s", "test_yield.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_yield.py::test_a 打开浏览器并打开百度首页
PASSED [ 33%]---test case a: 搜索 pytest---

test_yield.py::test_b
PASSED [ 66%]---test case b: 搜索 pytest---

test_yield.py::test_c
PASSED [100%]---test case c: 搜索 pytest---
执行teardown
关闭浏览器
============================== 3 passed in 0.05s ==============================
"""

(1) 用例异常

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
import pytest                                           # test_use_case_exception.py


@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")

yield
print("执行teardown")
print("关闭浏览器")


def test_a(open):
print("---test case a: 搜索 pytest---")
raise NameError # 模拟异常,不影响其他用例执行


def test_b(open):
print("---test case b: 搜索 pytest---")


def test_c(open):
print("---test case c: 搜索 pytest---")


if __name__ == "__main__":
pytest.main(["-s", "test_use_case_exception.py"])

""" !!!用例异常时,不影响yield实现teardown的执行。
============================= test session starts =============================
collecting ... collected 3 items

test_use_case_exception.py::test_a 打开浏览器并打开百度首页
FAILED [ 33%]---test case a: 搜索 pytest---

test_use_case_exception.py:12 (test_a)
open = None

def test_a(open):
print("---test case a: 搜索 pytest---")
> raise NameError # 模拟异常,不影响其他用例执行
E NameError

test_use_case_exception.py:15: NameError

test_use_case_exception.py::test_b
PASSED [ 66%]---test case b: 搜索 pytest---

test_use_case_exception.py::test_c
PASSED [100%]---test case c: 搜索 pytest---
执行teardown
关闭浏览器
========================= 1 failed, 2 passed in 0.45s =========================
"""

(2) setup异常

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
import pytest                                           # test_setup_exception.py


@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")
raise NameError # 模拟setup异常

yield
print("执行teardown")
print("关闭浏览器")


def test_a(open):
print("---test case a: 搜索 pytest---")


def test_b(open):
print("---test case b: 搜索 pytest---")


def test_c(open):
print("---test case c: 搜索 pytest---")


if __name__ == "__main__":
pytest.main(["-s", "test_setup_exception.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_setup_exception.py::test_a
ERROR [ 33%]打开浏览器并打开百度首页

test setup failed
@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")
> raise NameError # 模拟setup异常
E NameError

test_setup_exception.py:7: NameError

test_setup_exception.py::test_b
ERROR [ 66%]
test setup failed
@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")
> raise NameError # 模拟setup异常
E NameError

test_setup_exception.py:7: NameError

test_setup_exception.py::test_c
ERROR [100%]
test setup failed
@pytest.fixture(scope="module")
def open():
print("打开浏览器并打开百度首页")
> raise NameError # 模拟setup异常
E NameError

test_setup_exception.py:7: NameError
============================== 3 errors in 0.37s ==============================
"""

(3) 配合with使用*

1
2
3
4
5
6
7
8
import pytest                                            # test_with.py
import smtplib


@pytest.fixture(scope="module")
def smtp():
with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp:
yield smtp # smtp对象自动关闭时,with语句结束

5-4 addfinalizer终结函数

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
import pytest                                           # test_addfinalizer.py


@pytest.fixture(scope="module")
def test_addfinalizer(request):
print("打开浏览器")
test = "test_addfinalizer" # 前置操作setup

def fin(): # 后置操作teardown
print("关闭浏览器")

request.addfinalizer(fin)
return test # 返回前置操作的变量


def test_case(test_addfinalizer):
print("---test cases---", test_addfinalizer)

"""
============================= test session starts =============================
collecting ... collected 1 item

test_addfinalizer.py::test_case 打开浏览器
PASSED [100%]---test cases--- test_addfinalizer
关闭浏览器
============================== 1 passed in 0.04s ==============================
"""

5-5 调用fixture的3种方法

  • 调用fixture的3种方法
    • 函数或类的方法中传fixture的函数参数名称。
    • 用装饰器@pytest.mark.usefixtures()修饰。
    • 设置fixture的参数为autouse=True来自动使用。

(1) 用例fixture传参

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
import pytest                                           # test_calling_in_parameter.py


@pytest.fixture(scope="function")
def start(request):
print("---开始执行函数---")


def test_a(start):
print("---use case a---")


class TestCase:
def test_b(self, start):
print("---use case b---")

def test_c(self, start):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_calling_in_parameter.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_calling_in_parameter.py::test_a ---开始执行函数---
PASSED [ 33%]---use case a---

test_calling_in_parameter.py::TestCase::test_b ---开始执行函数---
PASSED [ 66%]---use case b---

test_calling_in_parameter.py::TestCase::test_c ---开始执行函数---
PASSED [100%]---use case c---
============================== 3 passed in 0.06s ==============================
"""

(2) 装饰器usefixtures

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
import pytest                                           # test_calling_usefixtures.py


@pytest.fixture(scope="function")
def start(request):
print("---开始执行函数---")


@pytest.mark.usefixtures("start")
def test_a(start):
print("---use case a---")


@pytest.mark.usefixtures("start")
class TestCase:
def test_b(self, start):
print("---use case b---")

def test_c(self, start):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_calling_usefixtures.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_calling_usefixtures.py::test_a ---开始执行函数---
PASSED [ 33%]---use case a---

test_calling_usefixtures.py::TestCase::test_b ---开始执行函数---
PASSED [ 66%]---use case b---

test_calling_usefixtures.py::TestCase::test_c ---开始执行函数---
PASSED [100%]---use case c---
============================== 3 passed in 0.10s ==============================
"""

(3) 设置autouse=True

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
import pytest                                           # test_calling_function.py


@pytest.fixture(scope="module", autouse=True)
def start(request):
print("\n---开始执行模块---")
print("module: %s" % request.module.__name__)
print("---打开浏览器---")

yield
print("---结束测试---")


@pytest.fixture(scope="function", autouse=True)
def open_home(request): # 函数实现
print("function: %s \n---返回首页---" % request.function.__name__)


def test_a():
print("---use case a---")


def test_b():
print("---use case b---")


if __name__ == "__main__":
pytest.main(["-s", "test_calling_function.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

test_calling_function.py::test_a
---开始执行模块---
module: test_calling_function
---打开浏览器---
function: test_a
---返回首页---
PASSED [ 50%]---use case a---

test_calling_function.py::test_b function: test_b
---返回首页---
PASSED [100%]---use case b---
---结束测试---
============================== 2 passed in 0.06s ==============================
"""
  • 设置autouse=True
    • start设置scope为module级别,在当前用例模块中只执行一次,autouse=True自动使用。
    • open_home设置scope为function级别,每个用例前都调用一次,autouse=True自动使用。
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
import pytest                                           # test_calling_class.py


@pytest.fixture(scope="module", autouse=True)
def start(request):
print("\n---开始执行模块---")
print("module: %s" % request.module.__name__)
print("---打开浏览器---")

yield
print("---结束测试---")


class TestCase:
@pytest.fixture(scope="function", autouse=True)
def open_home(self, request): # 类实现
print("function: %s \n---返回首页---" % request.function.__name__)

def test_a(self):
print("---use case a---")

def test_b(self):
print("---use case b---")


if __name__ == "__main__":
pytest.main(["-s", "test_calling_class.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

test_calling_class.py::TestCase::test_a
---开始执行模块---
module: test_calling_class
---打开浏览器---
function: test_a
---返回首页---
PASSED [ 50%]---use case a---

test_calling_class.py::TestCase::test_b function: test_b
---返回首页---
PASSED [100%]---use case b---
---结束测试---
============================== 2 passed in 0.04s ==============================
"""

6 自定义标记mark

  • 自定义标记mark
    • 自定义标记可以将一个Web项目划分为多个模块,然后指定模块名称进行执行。
    • APP自动化时,若想让Android与IOS共用一套代码,也可以使用标记指定名称。
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
import pytest                                           # test_mark.py


@pytest.mark.a
def test_a(): # 标记test_a()为a
print("---use case a---")


def test_b():
print("---use case b---")


class TestCase:
def test_c(self):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_mark.py", "-m=a"])

# pytest -v -m a test_mark.py
""" !此时a标签为未识别标签,没注册直接使用将弹出警告信息,不注册仍可挑选用例。
=========================== test session starts ===============================
...
collected 3 items / 2 deselected / 1 selected
test_mark.py::test_a PASSED [100%]
============================ warnings summary =================================
...DeprecationWarning: Using or importing the ABCs from 'collections' instead of from
'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
return isinstance(x, collections.Callable)

test_mark.py:4
...\test_mark.py:4: PytestUnknownMarkWarning: Unknown pytest.mark.a - is this a typo?
You can register custom marks to avoid this warning - for details,
see https://docs.pytest.org/en/stable/mark.html
@pytest.mark.a

-- Docs: https://docs.pytest.org/en/stable/warnings.html
================ 1 passed, 2 deselected, 1 warnings in 0.04s ==================
"""
  • 用例执行规则
    • 命令窗口下只运行用tagname标记的测试用例时,可以使用命令:pytest -v -m tagname
    • 若不想执行tagname标记的用例,文件内容对应修改,使用命令:pytest -v -m "not tagname"
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
import pytest                                           # test_mark.py


@pytest.mark.a
def test_a(): # 标记test_a()为a
print("---use case a---")


def test_b():
print("---use case b---")


class TestCase:
def test_c(self):
print("---use case c---")


if __name__ == "__main__":
pytest.main(["-s", "test_mark.py", "-m=not a"])

# pytest -v -m "not a" test_mark.py
"""
============================ test session starts ==============================
...
collected 3 items / 1 deselected / 2 selected
test_mark.py::test_b PASSED [ 50%]
test_mark.py::TestCase::test_c PASSED [100%]
============================= warnings summary ================================
...DeprecationWarning: Using or importing the ABCs from 'collections' instead of from
'collections.abc' is deprecated since Python 3.3, and in 3.10 it will stop working
return isinstance(x, collections.Callable)

test_mark.py:4
...\test_mark.py:4: PytestUnknownMarkWarning: Unknown pytest.mark.a - is this a typo?
You can register custom marks to avoid this warning - for details,
see https://docs.pytest.org/en/stable/mark.html
@pytest.mark.a

-- Docs: https://docs.pytest.org/en/stable/warnings.html
================ 2 passed, 1 deselected, 1 warnings in 0.05s ==================
"""

6-1 重写注册标签

  • 重写注册标签
    • 根目录创建conftest.py文件,在文件中重写pytest_configure函数的方式可以注册标签。
    • 函数名pytest_configure和参数config是固定的,唯一需要变更的是注册标签的名字和描述。

(1) conftest.py

1
2
3
4
def pytest_configure(config):                            # conftest.py
config.addinivalue_line(
"markers", "a: a test"
)

(2) test_mark.py

1
2
3
4
5
6
7
8
pytest -v -m a test_mark.py                              # test_mark.py内容不变
"""
=========================== test session starts ===============================
...
collected 3 items / 2 deselected / 1 selected
test_mark.py::test_a PASSED [100%]
===================== 1 passed, 2 deselected in 0.04s =========================
"""

6-2 配置注册标签

  • 配置注册标签
    • 还有一种更加便捷的注册标签的方式,即在pytest.ini配置文件中进行语法配置。
    • 同样在根目录创建pytest.ini文件,只需要在文件中编写如下配置内容就可以了。

(1) pytest.ini

1
2
3
4
5
[pytest]                                                 # pytest.ini


markers =
a: a tests

(2) test_mark.py

1
2
3
4
5
6
7
8
9
pytest -v -m "not a" test_mark.py                        # test_mark.py内容不变
""" !!!该方法便捷简单,实际自动化脚本开发时,更推荐使用。
============================ test session starts ==============================
...
collected 3 items / 1 deselected / 2 selected
test_mark.py::test_b PASSED [ 50%]
test_mark.py::TestCase::test_c PASSED [100%]
===================== 2 passed, 1 deselected in 0.03s =========================
"""

6-3 灵活挑选用例

(1) pytest.ini

1
2
3
4
5
6
7
[pytest]                                                 # pytest.ini


markers =
a: a tests
b: b tests
function: function tests

(2) test_mark_flexible.py

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
import pytest                                           # test_mark_flexible.py


@pytest.mark.a # 每个测试函数可以自定义多个标签
@pytest.mark.function # 实际开发中某些用例既可用作功能用例
def test_a(): # 也可以用作冒烟测试用例
print("---use case a---")


@pytest.mark.b
def test_b():
print("---use case b---")


@pytest.mark.function
class TestCase:
def test_c(self):
print("---use case c---")

"""
pytest -v -m a test_mark_flexible.py
pytest -v -m "not a" test_mark_flexible.py
pytest -v -m "a and b" test_mark_flexible.py
pytest -v -m "a or function" test_mark_flexible.py
"""

7 用例的参数化设置

  • 用例的参数化设置
    • 参数化方式有两种:parametrize参数化和fixture参数化。
    • 使用@pytest.mark.parametrize装饰器实现用例的参数化。
    • parametrize还可以结合内置函数,标记测试实例进行参数化。

7-1 parametrize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import pytest                                           # test_param.py


@pytest.mark.parametrize("test_input, expected", [("3+3", 6), ("2+4", 6), ("6*1", 6)])
def test_eval(test_input, expected):
assert eval(test_input) == expected


if __name__ == "__main__":
pytest.main(["-s", "test_param.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_param.py::test_eval[3+3-6] PASSED [ 33%]
test_param.py::test_eval[2+4-6] PASSED [ 66%]
test_param.py::test_eval[6*1-6] PASSED [100%]
============================== 3 passed in 0.67s ==============================
"""

(1) 结合内置函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import pytest                                           # test_param_function.py


@pytest.mark.parametrize("test_input, expected",
[("3+3", 6), ("2+4", 6), pytest.param("6*1", 6, marks=pytest.mark.xfail)])
def test_eval(test_input, expected):
assert eval(test_input) == expected


if __name__ == "__main__":
pytest.main(["-s", "test_param_function.py"])

"""
============================= test session starts =============================
collecting ... collected 3 items

test_param_function.py::test_eval[3+3-6] PASSED [ 33%]
test_param_function.py::test_eval[2+4-6] PASSED [ 66%]
test_param_function.py::test_eval[6*1-6] XPASS [100%]
======================== 2 passed, 1 xpassed in 0.14s =========================
"""

(2) 参数组合使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pytest                                           # test_param_combination.py


@pytest.mark.parametrize("x", [0, 1])
@pytest.mark.parametrize("y", [2, 3])
def test_case(x, y):
print("Test data: x---%s, y---%s" % (x, y))


if __name__ == "__main__":
pytest.main(["-s", "test_param_combination.py"])

"""
============================= test session starts =============================
collecting ... collected 4 items

test_param_combination.py::test_case[2-0] PASSED [ 25%]Test data: x---0, y---2
test_param_combination.py::test_case[2-1] PASSED [ 50%]Test data: x---1, y---2
test_param_combination.py::test_case[3-0] PASSED [ 75%]Test data: x---0, y---3
test_param_combination.py::test_case[3-1] PASSED [100%]Test data: x---1, y---3
============================== 4 passed in 0.05s ==============================
"""

(3) 函数传参操作

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
import pytest                                           # test_param_in.py

info = [("admin", "12345"), ("admin", "")] # 测试登录数据


def login(username, password):
print("账户: %s" % username)
print("密码: %s" % password)
if password:
return True
else:
return False


@pytest.mark.parametrize("username, password", info)
def test_login(username, password):
result = login(username, password)
assert result == True, "报错原因:密码为空"


if __name__ == "__main__":
pytest.main(["-s", "test_param_in.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

test_param_in.py::test_login[admin-12345] PASSED [ 50%]账户: admin
密码: 12345

test_param_in.py::test_login[admin-] FAILED [100%]账户: admin
密码:

test_param_in.py:14 (test_login[admin-])
False != True

Expected :True
Actual :False
<Click to see difference>

username = 'admin', password = ''

@pytest.mark.parametrize("username, password", info)
def test_login(username, password):
result = login(username, password)
> assert result == True, "报错原因:密码为空"
E AssertionError: 报错原因:密码为空
E assert False == True
E +False
E -True

test_param_in.py:18: AssertionError
========================= 1 failed, 1 passed in 0.41s =========================
"""

7-2 fixture传参

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
import pytest                                           # test_fixture.py

info = ["admin1", "admin2"] # 测试账号数据


@pytest.fixture(scope="module")
def login(request):
user = request.param
print("登陆账户: %s" % user)
return user


@pytest.mark.parametrize("login", info, indirect=True)
def test_login(login): # indirect=True声明login是一个函数
value = login
print("测试用例中登录的返回值: %s" % value)
assert value != ""


if __name__ == "__main__":
pytest.main(["-s", "test_fixture.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

test_fixture.py::test_login[admin1] 登陆账户: admin1
PASSED [ 50%]测试用例中登录的返回值: admin1

test_fixture.py::test_login[admin2] 登陆账户: admin2
PASSED [100%]测试用例中登录的返回值: admin2
============================== 2 passed in 0.64s ==============================
"""

(1) 传两个参数

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
import pytest                                           # test_fixture_two.py

info = [{"username": "admin1", "password": "12345"},
{"username": "admin2", "password": ""}] # 测试登录数据,传多个参数用字典存储


@pytest.fixture(scope="module")
def login(request):
user = request.param["username"]
password = request.param["password"]
print("登录账户: %s" % user)
print("登录密码: %s" % password)
if password:
return True
else:
return False


@pytest.mark.parametrize("login", info, indirect=True)
def test_login(login): # 登录用例
value = login
print("测试用例中登录的返回值: %s" % value)
assert value, "失败原因:密码为空"


if __name__ == "__main__":
pytest.main(["-s", "test_fixture_two.py"])

"""
============================= test session starts =============================
collecting ... collected 2 items

test_fixture_two.py::test_login[login0] 登录账户: admin1
登录密码: 12345
PASSED [ 50%]测试用例中登录的返回值: True

test_fixture_two.py::test_login[login1] 登录账户: admin2
登录密码:
FAILED [100%]测试用例中登录的返回值: False

test_fixture_two.py:18 (test_login[login1])
login = False

@pytest.mark.parametrize("login", info, indirect=True)
def test_login(login): # 登录用例
value = login
print("测试用例中登录的返回值: %s" % value)
> assert value, "失败原因:密码为空"
E AssertionError: 失败原因:密码为空
E assert False

test_fixture_two.py:23: AssertionError
========================= 1 failed, 1 passed in 0.35s =========================
"""

(2) 多个fixture

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
import pytest                                           # test_fixture_more.py

test_username = ["admin1", "admin2"] # 用例组合是2个参数的个数相乘:2*2
test_password = ["12345", "12346"]


@pytest.fixture(scope="module")
def input_username(request):
username = request.param
print("登录账户: %s" % username)
return username


@pytest.fixture(scope="module")
def input_password(request):
password = request.param
print("登录密码: %s" % password)
return password


@pytest.mark.parametrize("input_username", test_username, indirect=True)
@pytest.mark.parametrize("input_password", test_password, indirect=True)
def test_login(input_username, input_password):
u = input_username
p = input_password
print("测试数据: u---%s, p---%s" % (u, p))
assert p


if __name__ == "__main__":
pytest.main(["-s", "test_fixture_more.py"])

"""
============================= test session starts =============================
collecting ... collected 4 items

test_fixture_more.py::test_login[12345-admin1] 登录账户: admin1
登录密码: 12345
PASSED [ 25%]测试数据: u---admin1, p---12345

test_fixture_more.py::test_login[12345-admin2] 登录账户: admin2
PASSED [ 50%]测试数据: u---admin2, p---12345

test_fixture_more.py::test_login[12346-admin2] 登录密码: 12346
PASSED [ 75%]测试数据: u---admin2, p---12346

test_fixture_more.py::test_login[12346-admin1] 登录账户: admin1
PASSED [100%]测试数据: u---admin1, p---12346
============================== 4 passed in 0.06s ==============================
"""

8 命令行参数的配置

  • 命令行参数的配置
    • conftest.py文件中添加命令行选项,命令行传入参数--cmdopt,用例需用到时即调用cmdpot函数。
    • 配置文件pytest.ini也可以改变pytest的运行方式,是一个固定文件,读取配置信息,按指定方式运行。
    • PyTest框架的部分文件说明
      • conftest.py:参数用例的一些fixture配置。
      • setup.cfg:ini格式文件,影响setup.py的行为。
      • pytest.ini:主配置文件,改变PyTest的默认行为。
      • tox.ini:与pytest.ini类似,使用tox工具时才用。
      • __init__.py:识别该文件所在的文件夹为Python的package包。

8-1 conftest.py

1
2
3
4
5
6
7
8
9
10
11
12
13
import pytest                                           # conftest.py


def pytest_addoption(parser):
parser.addoption("--cmdopt",
action="store",
default="type1",
help="myoption: type1 or type2")


@pytest.fixture
def cmdopt(request):
return request.config.getoption("--cmdopt")

(1) 不带参数执行

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
import pytest                                           # test_sample.py


def test_answer(cmdopt):
if cmdopt == "type1":
print("---1---")
elif cmdopt == "type2":
print("---2---")
assert 0


if __name__ == "__main__":
pytest.main(["-s", "test_sample.py"])

# pytest -s test_sample.py # 不带参数执行,默认传type1
"""
=========================== test session starts ===============================
...
collected 1 item

test_sample.py ---1---
F

================================ FAILURES =====================================
_______________________________ test_answer ___________________________________
cmdopt = 'type1'

def test_answer(cmdopt):
if cmdopt == "type1":
print("---1---")
elif cmdopt == "type2":
print("---2---")
> assert 0
E assert 0

test_sample.py:9: AssertionError
========================== short test summary info ============================
FAILED test_sample.py::test_answer - assert 0
============================= 1 failed in 0.28s ===============================
"""

(2) 带上参数执行

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
# pytest -s test_sample.py --cmdopt=type2               # 带上参数执行
"""
=========================== test session starts ===============================
...
collected 1 item

test_sample.py ---2---
F

================================ FAILURES =====================================
_______________________________ test_answer ___________________________________
cmdopt = 'type2'

def test_answer(cmdopt):
if cmdopt == "type1":
print("---1---")
elif cmdopt == "type2":
print("---2---")
> assert 0
E assert 0

test_sample.py:9: AssertionError
========================== short test summary info ============================
FAILED test_sample.py::test_answer - assert 0
============================= 1 failed in 0.33s ===============================
"""

8-2 pytest.ini配置

  • pytest.ini配置
    • 一个项目有一个pytest.ini文件即可,放在项目的根目录下。
    • 命令窗口下使用pytest --help指令,可以查看到pytest.ini文件中的设置选项。
    • pytest.ini文件不能带中文注释,否则运行将报错:UnicodeDecodeError: 'gbk'...illegal multibyte
    • addopts参数默认更改命令行选项,例如:执行完用例需要生成报告,使用命令pytest --html=report.html
    • 将生成报告的命令加入pytest.ini文件中,下次直接执行pytest指令,PyTest就会默认带上这些新增的参数。
1
2
3
4
5
6
7
8
9
10
11
[pytest]                                                # pytest.ini,放在项目的根目录下


markers = # 自定义mark标记的配置注册
a: a tests
b: b tests
function: Function tests
webtest: Run the webtest case


addopts = --html=report.html # 执行完用例生成报告,需事先安装好插件
  • mark标记
    • 使用mark标记功能,对后续分类测试用例十分有用,将自定义标记写入pytest.ini文件中,方便管理。
    • 命令窗口对应pytest.ini文件所在的文件夹下,输入pytest --markers命令可以查看到已定义的标记。
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
import pytest                                           # test_xpass.py


def test_a():
print("---use case a---")


@pytest.mark.xfail()
def test_b():
x = "hello"
y = "hello world"
assert x == y


@pytest.mark.xfail()
def test_c():
x = "hello"
y = "hello world"
assert x != y


if __name__ == "__main__":
pytest.main(["-s", "test_xpass.py"]) # 执行前先将addopts = --html=report.html注释掉

"""
============================= test session starts =============================
collecting ... collected 3 items

test_xpass.py::test_a PASSED [ 33%]---use case a---

test_xpass.py::test_b XFAIL [ 66%]
@pytest.mark.xfail()
def test_b():
x = "hello"
y = "hello world"
> assert x == y
E AssertionError: assert 'hello' == 'hello world'
E - hello world
E + hello

test_xpass.py:12: AssertionError

test_xpass.py::test_c XPASS [100%]
=================== 1 passed, 1 xfailed, 1 xpassed in 0.33s ===================
"""
  • 禁用xpass*
    • 设置xfail_strict = true可以让标记为@pytest.mark.xfail但实际通过的用例被报告为失败。
    • test_xpass.py实例中,test_b和test_c都标记为失败,这里希望b和c用例不用执行就显示xfail。

(1) pytest.ini

1
2
3
4
[pytest]                                                 # pytest.ini,禁用XPASS


xfail_strict = true

(2) test_xpass.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 再次执行test_xpass.py,查看结果
""" !!!test_c的XPASS被强制性标记为FAILED。
============================= test session starts =============================
collecting ... collected 3 items

test_xpass.py::test_a PASSED [ 33%]---use case a---

test_xpass.py::test_b XFAIL [ 66%]
@pytest.mark.xfail()
def test_b():
x = "hello"
y = "hello world"
> assert x == y
E AssertionError: assert 'hello' == 'hello world'
E - hello world
E + hello

test_xpass.py:13: AssertionError

test_xpass.py::test_c FAILED [100%]
test_xpass.py:15 (test_c)
[XPASS(strict)]
============== 1 failed, 1 passed, 1 xfailed, 1 warning in 0.26s ==============
"""

9 生成html测试报告

  • 生成html测试报告
    • pytest-html:PyTest用于生成测试结果的html报告插件,pip install pytest-html进行安装。
      • 执行方法:pytest --html=report.html,当前目录中生成report.html报告文件。
      • 指定执行某个.py文件生成报告:pytest test_filename.py --html=report.html
      • 生成报告到当前文件夹的report目录中:pytest test_filename.py --html=./report/report.html
      • 上述方法生成的报告,css独立,分享报告时样式将丢失,为更好展示报告,可将css样式合并到html中。
      • report.html报告独立显示,可使用命令:pytest --html=report.html --self-contained-html
      • 用例执行失败重跑需依赖pytest-rerunfailures插件,命令安装:pip install pytest-rerunfailures
        • 重跑n次:pytest --reruns n,失败才重跑,成功用例使用无效。
        • 重跑n次,每次间隔m秒:pytest --reruns n --reruns-delay m
    • allure:report框架,支持Python的PyTest、Java的JUnit、TestNG等框架,可以集成到Jenkins上。

9-1 conftest.py

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
import pytest                                           # conftest.py,用例报错时进行截图的配置代码
from selenium import webdriver

driver: webdriver.Firefox = None # 需用到浏览器插件,提前配置好


@pytest.mark.hookwrapper
def pytest_runtest_makereport(item):
"""
当用例无法执行时,屏幕截图会自动显示在报告中。
:param item:
:return:
"""
pytest_html = item.config.pluginmanager.getplugin("html")
outcome = yield
report = outcome.get_result()
extra = getattr(report, "extra", [])
print("项目类型: ", type(item))
print("报告类型: ", type(report))
print("额外说明:", extra)

if report.when == "call" or report.when == "setup":
xfail = hasattr(report, "wasfail")
if (report.skipped and xfail) or (report.failed and not xfail):
file_name = report.nodeid.replace("::", "_") + ".png"
screen_img = _capter_screenshot()
if file_name:
html = "<div><img src='data:image/png;base64,%s' \
alt='screenshot' style='width:600px;height:300px;'" \
"onclick='window.open(this.src)' align='right'/></div>" % screen_img
extra.append(pytest_html.extras.html(html))
report.extra = extra


def _capter_screenshot():
"""
屏幕截图保存为base64并显示在html报告中。
:return:
"""
return driver.get_screenshot_as_base64()


@pytest.fixture(scope="session", autouse=True)
def browser():
global driver
if driver is None:
driver = webdriver.Firefox()
return driver

9-2 test_screenshot.py

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
import time                                             # test_screenshot.py
from selenium import webdriver


def test_get_title(browser):
"""
:浏览器类型: webdriver.firefox
"""
browser.get("https://stitch-top.github.io/") # GitHub网站访问较慢
time.sleep(5)
t = browser.title
assert t == "Stitch.top" # "Dr.626"
browser.close()


'''
============================= test session starts =============================
collecting ... collected 1 item

test_screenshot.py::test_get_title FAILED [100%]
项目类型: <class '_pytest.python.Function'>
报告类型: <class '_pytest.reports.TestReport'>
额外说明: []
项目类型: <class '_pytest.python.Function'>
报告类型: <class '_pytest.reports.TestReport'>
额外说明: []

test_screenshot.py:4 (test_get_title)
'Dr.626' != 'Stitch.top'

Expected :'Stitch.top'
Actual :'Dr.626'
<Click to see difference>

browser = <selenium.webdriver.firefox.webdriver.WebDriver (session="...")>

def test_get_title(browser):
"""
:浏览器类型: webdriver.firefox
"""
browser.get("https://stitch-top.github.io/") # GitHub网站访问较慢
time.sleep(5)
t = browser.title
> assert t == "Stitch.top" # "Dr.626"
E AssertionError: assert 'Dr.626' == 'Stitch.top'
E - Stitch.top
E + Dr.626

test_screenshot.py:12: AssertionError
项目类型: <class '_pytest.python.Function'>
报告类型: <class '_pytest.reports.TestReport'>
额外说明: []
============================= 1 failed in 42.29s ==============================
'''

Pytest 测试框架
https://stitch-top.github.io/2023/03/03/zi-dong-hua/at02-pytest/at01-pytest-ce-shi-kuang-jia/
作者
Dr.626
发布于
2023年3月3日 23:03:27
许可协议