pytest使用详解

1.pytest简介2.pytest的命名规则2.1 python的命名规则2.2 pytest的命名规则

3.pytest的运行方式3.1 主函数运行3.2 命令行运行3.3 pytest.ini配置文件方式运行(常用)

4.conftest.py4.1 什么是conftest.py4.2 实际的开发场景4.3 conftest.py的特点4.4 conftest.py的代码

5. Pytest Exit Code 含义清单6.pytest中控制测试用例的执行6.1.在第N个用例失败后,结束测试执行6.2指定执行的测试模块6.3 指定要执行的测试目录6.4 通过关键字过滤执行6.5通过 node id 指定测试用例6.6 通过标记表达式执行6.7通过包执行6.8多进程运行cases6.8测试用例的重试6.9改变执行测试用例的顺序

7.Pytest的setup和teardown函数8.pytest.fixture的详细使用8.1前言8.2fixture的优势8.3 fixture参数8.4测试用例如何调用fixture8.5fixture使用yield实现teardown8.6pytest.fixture中使用参数request

9. skip、skipif 跳过测试用例9.1 @pytest.mark.skip9.2 pytest.skip()函数基础使用9.3 pytest.skip(msg="",allow_module_level=False)9.4 @pytest.mark.skipif(condition, reason="")9.5 跳过标记9.6 pytest.importorskip( modname: str, minversion: Optional[str] = None, reason: Optional[str] = Nonse )9.6 使用自定义标记 mark

10.参数化 @pytest.mark.parametrize10.1 函数数据参数化

11 标记为失败函数和失败重试11.1 标记为预期失败的函数11.2 失败后重试

12.测试报告生成12.1 pytest-html生成测试报告12.2 pytest-testreporting 生成测试报告12.3 allure 生成测试报告

13.pytest中日志管理13.1如何管理Captured logging日志13.3 pytest中警告捕捉

14.小结

1.pytest简介

pytest是一个python的单元测试框架,也称为用例框架。 作用: 1)发现测试用例。从多个py文件中按照一定的规则找到测试用例 2)执行测试用例 3)判断测试结果,运用python断言 4)生成测试报告,可以使用allure、pytest-html、pytest-testreport

2.pytest的命名规则

2.1 python的命名规则

1)py文件全部小写,多个英文用_隔开 2)class名首字母大写,驼峰 3)函数和方法名小写,多个英文用_隔开 4)全局变量,前面要加global 5)常量字母必须全大写,如:AGE_OF_NICK

2.2 pytest的命名规则

1)模块名(py文件)必须是以test_开头或者_test结尾 2)测试类(class)必须以Test开头,并且不能带init方法,类里的方法必须以test_开头 3)测试用例(函数)必须以test_开头

3.pytest的运行方式

3.1 主函数运行

import pytest

def test_01():

print("this is {} testcase".format(__name__))

if __name__=='__main__':

pytest.main()

main中可使用的参数有:

参数描述案例-v输出调试信息。如:打印信息pytest.main([‘-v’,‘testcase/test_one.py’,‘testcase/test_two.py’])-s输出更详细的信息,如:文件名、用例名pytest.main([‘-vs’,‘testcase/test_one.py’,‘testcase/test_two.py’])-n多线程或分布式运行测试用例-x只要有一个用例执行失败,就停止执行测试pytest.main([‘-vsx’,‘testcase/test_one.py’])– maxfail出现N个测试用例失败,就停止测试pytest.main([‘-vs’,‘-x=2’,‘testcase/test_one.py’]–html=report.html生成测试报告pytest.main([‘-vs’,‘–html=./report.html’,‘testcase/test_one.py’])-m通过标记表达式执行-k根据测试用例的部分字符串指定测试用例,可以使用and,or

3.2 命令行运行

def test_a():

print("this is {} testcase".format('a'))

assert 1==1

def test_b():

print("this is {} testcase".format('b'))

assert 1==1

def test_c():

print("this is {} testcase".format('c'))

#终端输入:pytest ./testcase/test_one.py --html=./report/report.html

main中可使用的参数有:

参数描述案例-v输出调试信息。如:打印信息pytest -x ./testcase/test_one.py-q输出简单信息。pyets -q ./testcase/test_one.py-s输出更详细的信息,如:文件名、用例名pytest -s ./testcase/test_one.py-n多线程或分布式运行测试用例-x只要有一个用例执行失败,就停止执行测试pytest -x ./testcase/test_one.py– maxfail出现N个测试用例失败,就停止测试pytest --maxfail=2 ./testcase/test_one.py–html=report.html生成测试报告pytest ./testcase/test_one.py --html=./report/report.html-m通过标记表达式执行pytest -m slow ./testcase/test_one.py #@pytest.mark.slow装饰的所有用例-k根据测试用例的部分字符串指定测试用例,可以使用and,orpytest -k “MyClass and not method”,这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple注意:如果只执行 pytest ,会查找当前目录及其子目录下以test_.py或_test.py文件,找到文件后,在文件中找到以 test 开头函数并执行

3.3 pytest.ini配置文件方式运行(常用)

不管是mian执行方式还是命令执行,最终都会去读取pytest.ini文件 在项目的根目录下创建pytest.ini文件

[pytest]

addopts=-vs -m slow --html=./report/report.html

testpaths=testcase

test_files=test_*.py

test_classes=Test*

test_functions=test_*

makerers=

smock:冒烟测试用例

参数作用[pytest]用于标志这个文件是pytest的配置文件addopts命令行参数,多个参数之间用空格分隔testpaths配置搜索参数用例的范围python_files改变默认的文件搜索规则python_classes改变默认的类搜索规则python_functions改变默认的测试用例的搜索规则markers用例标记,自定义mark,需要先注册标记,运行时才不会出现warnings

pytset.ini文件尽可能不要出现中文。

4.conftest.py

4.1 什么是conftest.py

可以理解成一个专门存放fixture的配置文件

4.2 实际的开发场景

例如:多个测试用例文件(test_*.py)的所有用例都需要用登录功能来作为前置操作,那就不能把登录功能写到某个用例文件中去了

4.3 conftest.py的特点

pytest 会默认读取 conftest.py里面的所有 fixtureconftest.py 文件名称是固定的,不能改动conftest.py 只对同一个 package 下的所有测试用例生效不同目录可以有自己的 conftest.py,一个项目中可以有多个 conftest.py测试用例文件中不需要手动 import conftest.py,pytest 会自动查找

4.4 conftest.py的代码

目录如下:

最顶层的 conftest,一般写全局的 fixture,比如:在Web UI 自动化中,可能会写初始化 driver 的 fixture ./conftest.py

import pytest

@pytest.fixture(scope="session",autouse=True)

def login():

print("====根目录的session登录功能,返回账号===")

name = "testyy"

token = "npoi213bn4"

yield name, token

print("====根目录的session退出登录!!!====")

./testcase/conftest.py

import pytest

@pytest.fixture(scope="module", autouse=True)

def login_test_cse():

print("====testcase/module目录的登录功能,返回账号===")

name = "testyy"

token = "npoi213bn4"

yield name, token

print("====testcase/module目录的退出登录!!!====")

./testcase/test_three.py

def test_a():

print(f"=====test_three::test_a=====")

def test_b():

print(f"=====test_three::test_b=====")

./testcase/testone/conftest.py

import pytest

@pytest.fixture(scope="module",autouse=True)

def login_test_one():

print("====testcase/testone/moduled目录的登录功能,返回账号,token===")

name = "testyy"

token = "npoi213bn4"

yield name, token

print("====testcase/testone/moduled目录的退出登录!!!====")

./testcase/testone/test_one.py

def test_a():

print(f"=====test_one::test_a=====")

def test_b():

print(f"=====test_one::test_b=====")

./testcase/testone/test_two.py

def test_a():

print(f"=====test_one::test_a=====")

def test_b():

print(f"=====test_one::test_b=====")

./run_test.py

import pytest

if __name__ == '__main__':

pytest.main(["-s", "./testcase/"])

执行结果:

5. Pytest Exit Code 含义清单

Exit code 0 所有用例执行完毕,全部通过Exit code 1 所有用例执行完毕,存在Failed的测试用例Exit code 2 用户中断了测试的执行Exit code 3 测试执行过程发生了内部错误Exit code 4 pytest 命令行使用错误Exit code 5 未采集到可用测试用例文件

6.pytest中控制测试用例的执行

6.1.在第N个用例失败后,结束测试执行

pytest -x # 第01次失败,就停止测试

pytest --maxfail=2 # 出现2个失败就终止测试

6.2指定执行的测试模块

pytest ./testcase/test_one.py

6.3 指定要执行的测试目录

pytest ./testcase/

6.4 通过关键字过滤执行

pytest -k "MyClass and not method"

#这条命令会匹配文件名、类名、方法名匹配表达式的用例,这里这条命令会运行 TestMyClass.test_something, 不会执行 TestMyClass.test_method_simple

6.5通过 node id 指定测试用例

nodeid由模块文件名、分隔符、类名、方法名、参数构成,举例如下:

#运行指定模块的指定函数

pytest ./testcase/test_one.py::test_a

pytest ./testcase/test_one.py::test_a ./testcase/test_one.py::test_b

#运行指定模块的指定类中的方法

pytest test_mod.py::TestClass::test_method

6.6 通过标记表达式执行

pytest -m smock

#这条命令会执行被装饰器 @pytest.mark.smock 装饰的所有测试用例

6.7通过包执行

pytest --pyargs pkg.testing

#这条命令会自动导入包 pkg.testing,并使用该包所在的目录,执行下面的用例。

6.8多进程运行cases

#需要先安装pytest-xdist

#pip install -U pytest-xdist

pytest test_one.py -n NUM

#NUM填写并发的进程数

6.8测试用例的重试

在做接口测试时,有事会遇到503或短时的网络波动,导致case运行失败,而这并非是我们期望的结果,此时可以就可以通过重试运行cases的方式来解决。

#需要先安装pytest-rerunfailures

#pip install -U pytest-rerunfailures

pytest test_se.py --reruns NUM

#NUM 表示失败后的重试次数

6.9改变执行测试用例的顺序

默认执行顺序从上到下,改变测试用例的执行顺序,在测试用例上加上标记,实现如下: 1)先安装pytest-ordering:pip install pytest-ordering 2)在测试用例上打标记:pytest.mark.run(order=1)

import pytest

@pytest.mark.run(order=3)

def test_a():

print("this is {} testcase".format('a'))

assert 1==1

@pytest.mark.run(order=1)

def test_b():

print("this is {} testcase".format('b'))

assert 1==1

@pytest.mark.run(order=2)

def test_c():

print("this is {} testcase".format('c'))

if __name__=='__main__':

pytest.main()

执行结果:

test_one.py::test_b this is b testcase

PASSED

test_one.py::test_c this is c testcase

PASSED

test_one.py::test_a this is a testcase

PASSED

7.Pytest的setup和teardown函数

1.setup和teardown主要分为:模块级别、类级别、函数级别、方法级别、方法细化级别,分别如下:

方法描述setup_module()在每个模块之前执行teardown_module()在每个模块之后执行setup_class()在每个类之前执行,即:在一个测试类只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。teardown_class()在每个类之后执行,即:在一个测试类只运行一次setup_class和teardown_class,不关心测试类内有多少个测试函数。setup_function()在每个函数之前执行。teardown_function()在每个函数之后执行。setup_method()在每个方法之前执行teardown_method()在每个方法之后执行setup()在每个方法之前执行teardown()在每个方法之后执行2.扩展:在unittest中前后置只有setup和teardwon、setupClass和teardwonClass两大类。

#!/usr/bin/env python

# -*- coding:utf-8 -*-

"""

@Time:2022-10-0712:06

@Auth:ting.chun

@File:test_one.py.py

@IDE:PyCharm

"""

import pytest

def setup_module():

print("=====整个.py模块开始前只执行一次:打开浏览器=====")

def teardown_module():

print("=====整个.py模块结束后只执行一次:关闭浏览器=====")

def setup_function():

print("===每个函数级别用例开始前都执行setup_function===")

def teardown_function():

print("===每个函数级别用例结束后都执行teardown_function====")

def test_one():

print("one")

def test_two():

print("two")

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_three(self):

print("three")

def test_four():

print("four")

if __name__ == '__main__':

pytest.main(["-q", "-s", "-ra", "test_one.py"])

# 运行测试用例,显示总的结果信息,使用命令 pytest -ra

执行结果如下:

8.pytest.fixture的详细使用

8.1前言

虽然setup和teardown可以执行一些前置和后置操作,但是这种是针对整个脚本全局生效的如果有以下场景:1.用例一需要执行登录操作;2.用例二不需要执行登录操作;3.用例三需要执行登录操作,则setup和teardown则不满足要求。fixture可以让我自定义测试用例的前置条件

8.2fixture的优势

命名方式灵活,不限于setup和teardown两种命名conftest.py可以实现数据共享,不需要执行import 就能自动找到fixturescope=module,可以实现多个.py文件共享前置scope=“session” 以实现多个.py 跨文件使用一个 session 来完成多个用例

8.3 fixture参数

fixture修饰器来标记固定的工厂函数,在其他函数,模块,类或整个工程调用它时会被激活并优先执行,通常会被用于完成预置处理和重复操作。

方法:fixture(scope="function", params=None, autouse=False, ids=None, name=None)

常用参数:

scope:被标记方法的作用域

"function" (default):作用于每个测试方法,每个test都运行一次

"class":作用于整个类,每个class的所有test只运行一次

"module":作用于整个模块,每个module的所有test只运行一次

"session:作用于整个session(慎用),每个session只运行一次,即整个测试会话,即开始执行 Pytest 到结束测试

params:(list类型)提供参数数据,供调用标记方法的函数使用

autouse:是否自动运行,默认为False不运行,设置为True自动运行

8.4测试用例如何调用fixture

执行的三种方式:

1.将fixture名称作为测试用例的输入参数2.使用装饰器pytest.mark.usefixtures(fixturename)3.fixture设置autouse=True

扩展知识点:

在类声明上面加@pytest.mark.usefixtures(),代表这个类里面所有测试用例都会调用该 fixture可以叠加多个@pytest.mark.usefixtures(),先执行的放底层,后执行的放上层可以传多个 fixture 参数,先执行的放前面,后执行的放后面如果 fixture 有返回值,用@pytest.mark.usefixtures()是无法获取到返回值的,必须用传参的方式(方式一)

将fixture名称作为测试用例的输入参数:

class Test_ABC:

@pytest.fixture()

def before(self):

print("------->before")

def test_a(self,before): # ️ test_a方法传入了被fixture标识的函数,以参数的形式

print("------->test_a")

assert 1

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

test_abc.py

------->before # 发现before会优先于测试函数运行

------->test_a

使用装饰器pytest.mark.usefixtures(fixturename)

#!/usr/bin/env python

# -*- coding:utf-8 -*-

"""

@Time:2022-10-0712:06

@Auth:ting.chun

@File:test_one.py.py

@IDE:PyCharm

"""

import pytest

@pytest.fixture() # fixture标记的函数可以应用于测试类外部

def before():

print("------->before")

@pytest.mark.usefixtures("before")

class TestOne:

def setup(self):

print("------->setup")

def test_a(self):

print("------->test_a")

assert 1

def test_b(self):

print("------->test_b")

assert 1

if __name__ == '__main__':

pytest.main("-s test_one.py")

执行结果:

test_one.py::TestOne::test_a

------->before # 发现before自动优先于setup运行

------->setup

------->test_a

PASSED

test_one.py::TestOne::test_b ------->before

------->setup

------->test_b

PASSED

fixture设置autouse=True

import pytest

@pytest.fixture(autouse=True) # 设置为默认运行

def before():

print("------->before")

class Test_ABC:

def setup(self):

print("------->setup")

def test_a(self):

print("------->test_a")

assert 1

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

test_abc.py

------->before # 发现before自动优先于测试类运行

------->setup

------->test_a

fixture的作用域设置为:scope=“function”

import pytest

@pytest.fixture(scope='function',autouse=True) # 作用域设置为function,自动运行

def before():

print("------->before")

class Test_ABC:

def setup(self):

print("------->setup")

def test_a(self):

print("------->test_a")

assert 1

def test_b(self):

print("------->test_b")

assert 1

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

test_abc.py

------->before # 运行第一次

------->setup

------->test_a

.------->before # 运行第二次

------->setup

------->test_b

fixture的作用域设置为:scope=“class”

import pytest

@pytest.fixture(scope='class',autouse=True) # 作用域设置为class,自动运行

def before():

print("------->before")

class Test_ABC:

def setup(self):

print("------->setup")

def test_a(self):

print("------->test_a")

assert 1

def test_b(self):

print("------->test_b")

assert 1

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

test_abc.py

------->before # 发现只运行一次

------->setup

------->test_a

.

------->setup

------->test_b

.

fixture的返回值

import pytest

@pytest.fixture()

def need_data():

return 2 # 返回数字2

class Test_ABC:

def test_a(self,need_data):

print("------->test_a")

assert need_data != 3 # 拿到返回值做一次断言

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

test_abc.py

------->test_a

.

import pytest

@pytest.fixture(params=[1, 2, 3])

def need_data(request): # 传入参数request 系统封装参数

return request.param # 取列表中单个值,默认的取值方式

class Test_ABC:

def test_a(self,need_data):

print("------->test_a")

assert need_data != 3 # 断言need_data不等于3

if __name__ == '__main__':

pytest.main("-s test_abc.py")

执行结果:

# 可以发现结果运行了三次

test_abc.py

1

------->test_a

.

2

------->test_a

.

3

------->test_a

F

8.5fixture使用yield实现teardown

import pytest

@pytest.fixture(scope="session")

def open():

# 会话前置操作setup

print("===打开浏览器===")

test = "test"

yield test

# 会话后置操作teardown

print("==关闭浏览器==")

@pytest.fixture

def login(open):

# 方法级别前置操作setup

print(f"open方法yield的返回结果:{open}")

name = "==我是账号=="

pwd = "==我是密码=="

# 返回变量

yield name, pwd

# 方法级别后置操作teardown

print("登录成功")

def test_s1(login):

print("==用例1==")

# 返回的是一个元组

print(login)

# 分别赋值给不同变量

name, pwd = login

print(name, pwd)

assert "账号" in name

assert "密码" in pwd

def test_s2(login):

print("==用例2==")

print(login)

if __name__ == '__main__':

pytest.main("-s test_one.py")

执行结果:

test_one.py::test_s1 ===打开浏览器===

open方法yield的返回结果:test

==用例1==

('==我是账号==', '==我是密码==')

==我是账号== ==我是密码==

PASSED登录成功

test_one.py::test_s2 open方法yield的返回结果:test

==用例2==

('==我是账号==', '==我是密码==')

PASSED登录成功

==关闭浏览器==

yield和with结合使用

# 官方例子

@pytest.fixture(scope="module")

def smtp_connection():

with smtplib.SMTP("smtp.gmail.com", 587, timeout=5) as smtp_connection:

yield smtp_connection # provide the fixture value

该 smtp_connection 连接将测试完成执行后已经关闭,因为smtp_connection 对象自动关闭时, with 语句结束

8.6pytest.fixture中使用参数request

传单个参数

import pytest

@pytest.fixture()

def pre_data(request):

name = request.param

print(f"== 账号是:{name} ==")

return name

data = ["pyy1", "polo"]

ids = [f"pre_test_name is:{name}" for name in data]

#添加indirect=True参数是为了把 login 当成一个函数去执行,而不是一个参数,并且将 data 当做参数传入函数

#def test_name(login) ,这里的 login 是获取 fixture 返回的值

@pytest.mark.parametrize("pre_data", data, ids=ids, indirect=True)

def test_name(pre_data):

print(f" 测试用例的登录账号是:{pre_data} ")

多个参数

@pytest.fixture()

def pre_data(request):

param = request.param

print(f"账号是:{param['username']},密码是:{param['pwd']}")

return param

data = [

{"username": "name1", "pwd": "pwd1"},

{"username": "name2", "pwd": "pwd2"},

]

#如果需要传多个参数,需要通过字典去传

@pytest.mark.parametrize("pre_data", data, indirect=True)

def test_name_pwd(pre_data):

print(f"账号是:{pre_data['username']},密码是:{pre_data['pwd']}")

多个fixture(只加一个装饰器)

# 多个fixture

@pytest.fixture(scope="module")

def input_user(request):

user = request.param

print("登录账户:%s" % user)

return user

@pytest.fixture(scope="module")

def input_psw(request):

psw = request.param

print("登录密码:%s" % psw)

return psw

data = [

("name1", "pwd1"),

("name2", "pwd2")

]

@pytest.mark.parametrize("input_user,input_psw", data, indirect=True)

def test_more_fixture(input_user, input_psw):

print("fixture返回的内容:", input_user, input_psw)

多个fixture(叠加装饰器)

# 多个fixture

@pytest.fixture(scope="function")

def input_user(request):

user = request.param

print("登录账户:%s" % user)

return user

@pytest.fixture(scope="function")

def input_psw(request):

psw = request.param

print("登录密码:%s" % psw)

return psw

name = ["name1", "name2"]

pwd = ["pwd1", "pwd2"]

@pytest.mark.parametrize("input_user", name, indirect=True)

@pytest.mark.parametrize("input_psw", pwd, indirect=True)

def test_more_fixture(input_user, input_psw):

print("fixture返回的内容:", input_user, input_psw)

9. skip、skipif 跳过测试用例

9.1 @pytest.mark.skip

跳过执行测试用例,有可选参数 reason:跳过的原因,会在执行结果中打印

@pytest.mark.skip可以加在函数上,类上,类方法上如果加在类上面,类里面的所有测试用例都不会执行

import pytest

@pytest.fixture(autouse=True)

def login():

print("====登录====")

def test_case01():

print("我是测试用例11111")

@pytest.mark.skip(reason="不执行该用例!!因为没写好!!")

def test_case02():

print("我是测试用例22222")

class Test1:

def test_1(self):

print("%% 我是类测试用例1111 %%")

@pytest.mark.skip(reason="不想执行")

def test_2(self):

print("%% 我是类测试用例2222 %%")

@pytest.mark.skip(reason="类也可以跳过不执行")

class TestSkip:

def test_1(self):

print("%% 不会执行 %%")

9.2 pytest.skip()函数基础使用

作用:在测试用例执行期间强制跳过不再执行剩余内容 类似:在Python的循环里面,满足某些条件则break 跳出循环

def test_function():

n = 1

while True:

print(f"这是我第{n}条用例")

n += 1

if n == 5:

pytest.skip("我跑五次了不跑了")

执行结果:

9.3 pytest.skip(msg=“”,allow_module_level=False)

当 allow_module_level=True 时,可以设置在模块级别跳过整个模块

import sys

import pytest

if sys.platform.startswith("win"):

pytest.skip("skipping windows-only tests", allow_module_level=True)

@pytest.fixture(autouse=True)

def login():

print("====登录====")

def test_case01():

print("我是测试用例11111")

9.4 @pytest.mark.skipif(condition, reason=“”)

方法: skipif(condition, reason=None) 参数: condition:跳过的条件,必传参数 reason:标注原因,必传参数 使用方法: @pytest.mark.skipif(condition, reason=“xxx”)

import pytest

class Test_ABC:

def setup_class(self):

print("------->setup_class")

def teardown_class(self):

print("------->teardown_class")

def test_a(self):

print("------->test_a")

assert 1

@pytest.mark.skipif(condition=2>1,reason = "跳过该函数") # 跳过测试函数test_b

def test_b(self):

print("------->test_b")

assert 0

执行结果:

test_abc.py

------->setup_class

------->test_a #只执行了函数test_a

.

------->teardown_class

s # 跳过函数

9.5 跳过标记

可以将 pytest.mark.skip 和 pytest.mark.skipif 赋值给一个标记变量在不同模块之间共享这个标记变量若有多个模块的测试用例需要用到相同的 skip 或 skipif ,可以用一个单独的文件去管理这些通用标记,然后适用于整个测试用例集

# 标记

skipmark = pytest.mark.skip(reason="不能在window上运行=====")

skipifmark = pytest.mark.skipif(sys.platform == 'win32', reason="不能在window上运行啦啦啦=====")

@skipmark

class TestSkip_Mark(object):

@skipifmark

def test_function(self):

print("测试标记")

def test_def(self):

print("测试标记")

@skipmark

def test_skip():

print("测试标记")

9.6 pytest.importorskip( modname: str, minversion: Optional[str] = None, reason: Optional[str] = Nonse )

作用:如果缺少某些导入,则跳过模块中的所有测试 参数列表

modname:模块名minversion:版本号reason:跳过原因,默认不给也行

pexpect = pytest.importorskip("pexpect", minversion="0.3")

@pexpect

def test_import():

print("test")

9.6 使用自定义标记 mark

前言

pytest可以支持自定义标记,自定义标记可以把一个web项目划分为多个模块,然后指定模块名称执行譬如我们可以标明哪些用例在window上执行,哪些用例在mac上执行,在运行的时候指定mark就行

import pytest

@pytest.mark.model

def test_model_a():

print("执行test_model_a")

@pytest.mark.regular

def test_regular_a():

print("test_regular_a")

@pytest.mark.model

def test_model_b():

print("test_model_b")

@pytest.mark.regular

class TestClass:

def test_method(self):

print("test_method")

def testnoMark():

print("testnoMark")

命令运行 pytest -s -m model test_one.py

如何避免warnings

创建一个 pytest.ini 文件加上自定义markpytest.ini 需要和运行的测试用例同一个目录,或在根目录下作用于全局

[pytest] markers = model: this is model mark

如果不想标记 model 的用例

pytest -s -m " not model" test_one.py

如果想执行多个自定义标记的用例

pytest -s -m “model or regular” 08_mark.py

10.参数化 @pytest.mark.parametrize

pytest允许在多个级别启用测试化参数: 1)pytest.fixture()允许fixture有参数化功能 2)pytest.mark.parametrize 允许在测试函数和类中定义多组参数和fixtures 3)pytest_generate_tests允许定义自定义参数化方案或扩展

def parametrize(self,argnames, argvalues, indirect=False, ids=None, scope=None): argnames: 含义:参数值列表 格式:字符串"arg1,arg2,arg3" 例如: @pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”)]) argvalues: 含义:参数值列表 格式:必须是列表,如:[ val1,val2,val3 ] 如果只有一个参数,里面则是值的列表如:@pytest.mark.parametrize(“username”, [“yy”, “yy2”, “yy3”]) 如果有多个参数例,则需要用元组来存放值,一个元组对应一组参数的值,如:@pytest.mark.parametrize(“name,pwd”, [(“yy1”, “123”), (“yy2”, “123”), (“yy3”, “123”)]) ids: 含义:用例的id 格式:传一个字符串列表 作用:可以标识每一个测试用例,自定义测试数据结果的显示,为了增加可读性 indirect: 作用:如果设置成 True,则把传进来的参数当函数执行,而不是一个参数(下一篇文章即讲解

@pytest.mark.parametrize("test_input,expected", [("3+5", 8), ("2+4", 6), ("6*9", 42)])

def test_eval(test_input, expected):

print(f"测试数据{test_input},期望结果{expected}")

assert eval(test_input) == expected

10.1 函数数据参数化

方便测试函数对测试属于的获取。 方法: parametrize(argnames, argvalues, indirect=False, ids=None, scope=None) 常用参数: argnames:参数名 argvalues:参数对应值,类型必须为list 当参数为一个时格式:[value] 当参数个数大于一个时,格式为:[(param_value1,param_value2…),(param_value1,param_value2…)] 使用方法: @pytest.mark.parametrize(argnames,argvalues) ️ 参数值为N个,测试方法就会运行N次

单个参数

import pytest

class Test_ABC:

def setup_class(self):

print("------->setup_class")

def teardown_class(self):

print("------->teardown_class")

@pytest.mark.parametrize("a",[3,6]) # a参数被赋予两个值,函数会运行两遍

def test_a(self,a): # 参数必须和parametrize里面的参数一致

print("test data:a=%d"%a)

assert a%3 == 0

执行结果:

test_abc.py

------->setup_class

test data:a=3 # 运行第一次取值a=3

.

test data:a=6 # 运行第二次取值a=6

.

------->teardown_class

多个参数

import pytest

class Test_ABC:

def setup_class(self):

print("------->setup_class")

def teardown_class(self):

print("------->teardown_class")

@pytest.mark.parametrize("a,b",[(1,2),(0,3)]) # 参数a,b均被赋予两个值,函数会运行两遍

def test_a(self,a,b): # 参数必须和parametrize里面的参数一致

print("test data:a=%d,b=%d"%(a,b))

assert a+b == 3

执行结果:

test_abc.py

------->setup_class

test data:a=1,b=2 # 运行第一次取值 a=1,b=2

.

test data:a=0,b=3 # 运行第二次取值 a=0,b=3

.

------->teardown_class

函数返回值作为参数

import pytest

def return_test_data():

return [(1,2),(0,3)]

class Test_ABC:

def setup_class(self):

print("------->setup_class")

def teardown_class(self):

print("------->teardown_class")

@pytest.mark.parametrize("a,b",return_test_data()) # 使用函数返回值的形式传入参数值

def test_a(self,a,b):

print("test data:a=%d,b=%d"%(a,b))

assert a+b == 3

执行结果:

test_abc.py

------->setup_class

test data:a=1,b=2 # 运行第一次取值 a=1,b=2

.

test data:a=0,b=3 # 运行第二次取值 a=0,b=3

.

------->teardown_class

“笛卡尔积”,多个参数化装饰器

一个函数或一个类可以装饰多个 @pytest.mark.parametrize这种方式,最终生成的用例数是 nm,比如上面的代码就是:参数a的数据有 3 个,参数b的数据有 2 个,所以最终的用例数有 32=6 条当参数化装饰器有很多个的时候,用例数都等于 nnnn…

# 笛卡尔积,组合数据

data_1 = [1, 2, 3]

data_2 = ['a', 'b']

@pytest.mark.parametrize('a', data_1)

@pytest.mark.parametrize('b', data_2)

def test_parametrize_1(a, b):

print(f'笛卡尔积 测试数据为 : {a},{b}')

参数化,标记数据

# 标记参数化

@pytest.mark.parametrize("test_input,expected", [

("3+5", 8),

("2+4", 6),

pytest.param("6 * 9", 42, marks=pytest.mark.xfail),

pytest.param("6*6", 42, marks=pytest.mark.skip)

])

def test_mark(test_input, expected):

assert eval(test_input) == expected

11 标记为失败函数和失败重试

11.1 标记为预期失败的函数

标记测试函数为失败函数

方法:

xfail(condition=None, reason=None, raises=None, run=True, strict=False)

常用参数:

condition:预期失败的条件,必传参数

reason:失败的原因,必传参数

使用方法:

@pytest.mark.xfail(condition, reason="xx")

import pytest

class Test_ABC:

def setup_class(self):

print("------->setup_class")

def teardown_class(self):

print("------->teardown_class")

def test_a(self):

print("------->test_a")

assert 1

@pytest.mark.xfail(2 > 1, reason="标注为预期失败") # 标记为预期失败函数test_b

def test_b(self):

print("------->test_b")

assert 0

执行结果:

test_abc.py

------->setup_class

------->test_a

.

------->test_b

------->teardown_class

x # 失败标记

11.2 失败后重试

需安装第三方插件:pytest-rerun、pytest-rerunfailures

失败重试:【–reruns=1】,用例执行失败后,会立即开始重试一次此用例,再执行下一条用例失败重运行:【–if】 ,用例集或用例执行完成之后,再次pytest.main(),会收集失败的用例,再次运行;如果没有失败的用例,会执行全部 一个run文件,可以同时写多条pytest.main(),执行pytest的命令

if __name__=="__main__":

pytest.main(['-s','test_firstFile.py']) # 第一次运行,如果有失败的用例/第一次没有失败的用例

pytest.main(['-s','--lf','test_firstFile.py']) # 收集到第一次失败的用例,进行执行/则运行全部

注意:如果用例数较多,第一次运行全部成功的情况,第二个pytest.main(),是会收集所有的用例再执行一遍;建议使用失败重试次数(–reruns=1),失败一次后,立刻执行一次,也可减少用例的失败率

失败重试方式: 1、可在命令行 –reruns=1 reruns_delay=2 失败后重运行1次,延时2s 2、使用装饰器进行失败重运行 @pytest.mark.flaky(reruns=1, reruns_delay=2) 使用方式: 命令行参数:–reruns n(重新运行次数),–reruns-delay m(等待运行秒数) 装饰器参数:reruns=n(重新运行次数),reruns_delay=m(等待运行秒数)

重新运行所有失败的用例:

#运行失败的 fixture 或 setup_class 也将重新执行 pytest --reruns=5

添加重新运行的延时:

#要在两次重试之间增加延迟时间,使用 --reruns-delay 命令行选项,指定下次测试重新开始之前等待的秒数 pytest.main( [‘-vs’,‘–reruns=5’,‘–reruns_delay=10’,‘./testcase/test_debug.py’,‘–report=_report.html’])

重新运行指定的测试用例:

#要指定某些测试用例时,需要添加 flaky 装饰器@pytest.mark.flaky(reruns=5) ,它会在测试失败时自动重新运行,且需要指定最大重新运行的次数

import pytest

@pytest.mark.flaky(reruns=5)

def test_example():

import random

assert random.choice([True, False, False])

#同样的,这个也可以指定重新运行的等待时间

@pytest.mark.flaky(reruns=5, reruns_delay=2)

def test_example():

import random

assert random.choice([True, False, False])

注意: 1.如果指定了用例的重新运行次数,在命令行添加的 --reruns 对这些用例是不会生效的 2.不可以和 fixture 装饰器@pytest.fixture()一起使用 3.该插件与 pytest-xdist 的 --looponfail 标志不兼容 4.该插件与核心 --pdb 标志不兼容

12.测试报告生成

12.1 pytest-html生成测试报告

安装插件:

pip3 install pytest-html

执行方式:

#会在当前目录下创建一个 report.html 的测试报告 pytest --html=report.html

合并CSS:

#上面命令生成的报告,css 是独立的,分享报告的时候样式会丢失,为了更好的分享发邮件展示报告,可以把css样式合并到html里 pytest --html=report.html --self-contained-html

12.2 pytest-testreporting 生成测试报告

下载插件:pip3 install pytest-testreport

pytest -vs --reruns 5 ./testcase/test_debug.py --report=_report.html

12.3 allure 生成测试报告

13.pytest中日志管理

13.1如何管理Captured logging日志

https://cloud.tencent.com/developer/article/2204340

13.3 pytest中警告捕捉

https://blog.csdn.net/nxy_wuhao/article/details/116601291

14.小结

参考文档:https://blog.csdn.net/nxy_wuhao/category_10898993.html

文章来源

评论可见,请评论后查看内容,谢谢!!!
 您阅读本篇文章共花了: