pytest 相关总结

0.pytest 常用插件

  • pytest-html
  • pytest-xdist
  • pytest-ordering
  • pytest-rerunfailures
  • allure-pytest
  • pytest-base-url
  • pytest

1.pytest 常用命令参数

  • -h: 查看所有可用参数
  • -s:输出调试信息,包括 print 打印的信息。
  • -v:显示更详细的信息。
  • -k=value:用例的 nodeid 包含 value 值则用例被执行。
  • -n=num:启用多线程或分布式运行测试用例。需要安装 pytest-xdist 插件模块。
  • -m = 标签名:执行被 @pytest.mark. 标签名 标记的用例。
  • -x:只要有一个用例执行失败就停止当前线程的测试执行。
  • –maxfail=num:与 - x 功能一样,只是用例失败次数可自定义。
  • –reruns=num:失败用例重跑 num 次。需要安装 pytest-rerunfailures 插件模块。

2.pytest 默认的测试用例规则

一般在项目当中新建一个用例包

  • testcases
    • 创建很多的 py 文件当做用例使用

注意点:

  • 包名和模块名以及用例名(函数,方法)必须符合以 test 开头或者 test 结尾
  • 测试用例类必须以 Test 开头,而且这个测试类不能有 init 方法
  • 以上规则属于 pytest 默认的形式,如果需要修改可以配置 pytest.ini 进行改变

测试用例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 测试函数
def test_login_01():
print("1.打开页面")
print("2.输入账号密码")
print("3.点击登录按钮")

# 测试类
class TestLogin_01:
# 实例方法
def test_login_02(self):
print("实例方法1:点击登录页面")
print("实例方法2.输入账号密码")
print("实例方法3.点击登录按钮")

# 类方法
@classmethod
def test_login_03(cls):
print("类方法1.点击登录页面")
print("类方法2.输入账号密码")
print("类方法3.点击登录按钮")

3.pytest 用例执行的两种方式

1. 使用命令 pytest -vs

1
pytest -vs

2. 使用主函数

1
2
3
4
5
6
7
# run.py
import pytest

if __name__ == '__main__':
pytest.main([])
# 带参数执行
# pytest.main(['-v', '-s'])

4.pytest 标记跳过用例

跳过用例的方式有 2 中类型:

  • 无条件跳过

    1
    2
    3
    4
    5
    6
    @pytest.mark.skipif(reason='无条件跳过该测试用例')
    def test_login_01():
    print("1.打开页面")
    print("2.输入账号密码")
    print("3.点击登录按钮")

  • 有条件跳过

    • 满足条件就跳过不执行

      1
      2
      3
      4
      5
      @pytest.mark.skipif(3>2, reason='如果前面条件为真,则跳过该用例,不执行')
      def test_login_04():
      print("测试函数 4:打开页面")
      print("测试函数 4:输入账号密码")
      print("测试函数 4:点击登录按钮")
    • 不满足条件,就不跳过,正常执行

      1
      2
      3
      4
      5
      @pytest.mark.skipif(3<2, reason='如果前面条件为假, 则不跳过用例正常执行')
      def test_login_05():
      print("测试函数 5:打开页面")
      print("测试函数 5:输入账号密码")
      print("测试函数 5:点击登录按钮")

5.pytest 控制测试用例的执行顺序

默认是按照文件名以及用例名的先后顺序进行执行的,想要改变默认执行规则,可以进行修改调整。

  • pytest-ordring ??? 这里也没有用这个包的内容呢??
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@pytest.mark.run(order=3)
def test_login_06():
print("测试函数 6:打开页面")
print("测试函数 6:输入账号密码")
print("测试函数 6:点击登录按钮")


@pytest.mark.run(order=2)
def test_login_07():
print("测试函数 7:打开页面")
print("测试函数 7:输入账号密码")
print("测试函数 7:点击登录按钮")

# 使用@pytest.mark.run(order=1),order 的数字越小,执行顺序越靠前,没有该装饰器的则按照原有的方式执行
@pytest.mark.run(order=1)
def test_login_08():
print("测试函数 8:打开页面")
print("测试函数 8:输入账号密码")
print("测试函数 8:点击登录按钮")

6.pytest 标记失败的用例

标记预期会出现异常或者失败的测试用例,只有出现异常才符合预期,如果不出现异常反而不符合预期。

  • 正常预期失败

    1
    2
    3
    4
    5
    6
    @pytest.mark.xfail(reason='出现了被除数为 0 的情况')
    def test_login_09():
    print("测试函数 9:打开页面")
    print("测试函数 9:输入账号密码")
    print(1/0)
    print("测试函数 9:点击登录按钮")
  • 意外预期通过

    1
    2
    3
    4
    5
    6
    @pytest.mark.xfail(reason='意外通过了被除数为 0 的情况')
    def test_login_10():
    print("测试函数 10:打开页面")
    print("测试函数 10:输入账号密码")
    # print(1 / 0)
    print("测试函数 10:点击登录按钮")

执行结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
# ......

testcases/test_01.py::test_login_09 测试函数 9:打开页面
测试函数 9:输入账号密码
XFAIL (出现了被除数为 0 的情况)
testcases/test_01.py::test_login_10 测试函数 10:打开页面
测试函数 10:输入账号密码
测试函数 10:点击登录按钮
XPASS (意外通过了被除数为 0 的情况)

============================================== 6 passed, 2 skipped, 1 xfailed, 1 xpassed in 0.06s ==============================================
(.venv) ➜ StudyPytest

7.pytest 标记参数化

参数化的使用场景主要是针对同样的用例执行流程,输入不同的数据,结合参数进行数据驱动的测试。

1
2
3
4
5
6
7
8
@pytest.mark.parametrize(['username', 'password'], [('13100000001', 'pass01'),
('13100000002', 'pass02'),
('13100000003', 'pass03')])
def test_login_11(username, password):
print("测试函数 11:打开页面")
print("测试函数 11:输入账号", username)
print("测试函数 11:输入密码", password)
print("测试函数 11:点击登录按钮")

一个用例可以输入不同组的参数进行数据驱动测试,有多少个参数,用例就执行多少遍

从 excel 中提取参数化数据进行测试

只要返回的参数是

从 csv 中提取参数化数据进行测试

8. pytest 的 setup/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
import pytest


def setup_module(module):
# 模块级别的 setup
print("setup_module")


def teardown_module(module):
# 模块级别的 teardown
print("teardown_module")


class Test02:

@classmethod
# 类级别的 setup
def setup_class(cls):
print("setup_class")

@classmethod
# 类级别的 teardown
def teardown_class(cls):
print("teardown_class")

def setup_method(self, method):
# 方法级别的 setup
print("setup_method")

def teardown_method(self, method):
# 方法级别的 setup
print("teardown_method")

def test_02_01(self):
assert 3 > 2, '3确实大于2'

def test_02_02(self):
assert 3 < 2, '失败'

def test_02_03(self):
assert 1 / 0 == 0, '报错了'

执行结果:

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
(.venv) ➜  StudyPytest pytest -vs testcases/test_02.py::Test02
============================================================= test session starts ==============================================================
platform darwin -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 -- /Users/candy/Project/PycharmProjects/StudyPytest/.venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.12.4', 'Platform': 'macOS-13.6.7-x86_64-i386-64bit', 'Packages': {'pytest': '8.3.2', 'pluggy': '1.5.0'}, 'Plugins': {'html': '4.1.1', 'metadata': '3.1.1', 'rerunfailures': '14.0', 'allure-pytest': '2.13.5', 'ordering': '0.6', 'base-url': '2.1.0', 'xdist': '3.6.1'}, 'Base URL': ''}
rootdir: /Users/candy/Project/PycharmProjects/StudyPytest
plugins: html-4.1.1, metadata-3.1.1, rerunfailures-14.0, allure-pytest-2.13.5, ordering-0.6, base-url-2.1.0, xdist-3.6.1
collected 3 items

testcases/test_02.py::Test02::test_02_01
setup_module # 先启动模块级别 setup
setup_class # 在启动类级别 setup
setup_method # 在启动方法级别 setup
PASSED
teardown_method # 方法执行完毕 启动方法级别 teardown

testcases/test_02.py::Test02::test_02_02
setup_method # 第二个用例执行前,继续启动方法级别 setup
FAILED
teardown_method # 执行完毕后,启动方法级别 teardown

testcases/test_02.py::Test02::test_02_03 setup_method
FAILEDteardown_method
teardown_class
teardown_module

9.pytest 前后置 fixture 固件

当用例执行之前或者执行完成后需要固定的测试环境可以卸载 fixture 当中,然后进行使用。

(这个相对于之前的 setup,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
# conftest.py
from _pytest.fixtures import fixture


# fixture的作用范围:session > module > class > function, 不写默认为 function
@fixture(scope='function')
def e_x_function():
print('function级别开始')
yield
print('function级别结束')


@fixture(scope='class')
def e_x_class():
print('class级别开始')
yield
print('class级别结束')


@fixture(scope='module')
def e_x_module():
print('module级别开始')
yield
print('module级别结束')


@fixture(scope='session')
def e_x_session():
print('session级别开始')
yield
print('session级别结束')


@fixture
def e_x_fixture():
print('不写范围function级别开始')
yield
print('不写范围function级别结束')

测试用例中使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Test03:
def test_03_01(self, e_x_function):
assert 3 > 2, '为真,通过0301'

def test_03_02(self, e_x_class, e_x_function):
assert 3 < 2, '为假,不通过0302'

def test_03_03(self, e_x_class, e_x_module, e_x_function):
assert True, '为真,通过0303'


def test_03_04(e_x_module, e_x_function):
assert True, '为真,通过 0304'

def test_03_05(e_x_fixture):
assert True, '为真,通过 0304'

执行结果:

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
(.venv) ➜  StudyPytest pytest -vs -k=test_03
============================================================= test session starts ==============================================================
platform darwin -- Python 3.12.4, pytest-8.3.2, pluggy-1.5.0 -- /Users/candy/Project/PycharmProjects/StudyPytest/.venv/bin/python
cachedir: .pytest_cache
metadata: {'Python': '3.12.4', 'Platform': 'macOS-13.6.7-x86_64-i386-64bit', 'Packages': {'pytest': '8.3.2', 'pluggy': '1.5.0'}, 'Plugins': {'html': '4.1.1', 'metadata': '3.1.1', 'rerunfailures': '14.0', 'allure-pytest': '2.13.5', 'ordering': '0.6', 'base-url': '2.1.0', 'xdist': '3.6.1'}, 'Base URL': ''}
rootdir: /Users/candy/Project/PycharmProjects/StudyPytest
plugins: html-4.1.1, metadata-3.1.1, rerunfailures-14.0, allure-pytest-2.13.5, ordering-0.6, base-url-2.1.0, xdist-3.6.1
collected 21 items / 17 deselected / 4 selected

testcases/test_03.py::Test03::test_03_01
function级别开始
PASSED
function级别结束

testcases/test_03.py::Test03::test_03_02
class级别开始
function级别开始
FAILED
function级别结束

testcases/test_03.py::Test03::test_03_03
module级别开始
function级别开始
PASSED
function级别结束
class级别结束

testcases/test_03.py::test_03_04
function级别开始
PASSED
function级别结束
module级别结束

testcases/test_03.py::test_03_05
不写范围function级别开始
PASSED
不写范围function级别结束
module级别结束

9.pytest.ini 配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[pytest]
# 添加额外的测试文件或目录到测试搜索路径
testpaths = tests

# 指定测试文件的模式,例如 test_*.py 或者 *_test.py
python_files = test_*.py

# 指定测试目录的模式
testpaths =

# 指定 pytest 的插件
addopts = -v

# 配置日志记录
log_cli = true
log_cli_level = INFO

# 配置覆盖率报告
cov = my_package
cov_config = .coveragerc
cov_report = term-missing:skip-covered