一、pytest常用插件
- pytest-html 生成测试报告
- pytest-xdist 多线程运行
- pytest-ordering 执行顺序
- pytest-rerunfailures 失败用例重跑
- allure-pytest 用于在运行测试时收集 Allure 格式的测试结果(如用例状态、步骤、附件、日志等)
- pytest-base-url 管理用例的基础路径
- pytest 框架本身
二、pytest默认的测试用例规则
- 包名和模块名以及用例名(函数、方法)必须符合以test_开头或者 _test结尾
- 测试用例类必须以Test开头,而且测试类不能存在init魔法函数
- 以上规则属于pytest的默认形式,需要修改可以修改pytest.ini修改
三、pytest的运行方式
3.1主函数方式执行
pytest.main()
参数详解:
-
-s:表示输出调试信息,包括print打印的信息
-
-v表示详细的信息,一般以"-vs"使用
-
-n支持多线程运行,比如 -n 2
-
--reruns num :表示失败用例重跑,比如pytest.main(['-vs','./testcase','-n=2'])
-
-x表示只要有一个用例报错,那么测试停止
-
--maxfail=2出现2个用例失败就停止
-
-k=value:被指定执行的用例
-
--html ./report/report.html 指定默认报告生成的路径。与allure生成的报告不相同
-
指定运行模块pytest.main(['-vs', './testcase/test_01.py')
-
指定运行目录pytest.main(['-vs', './testcase/')
-
通过nodeid指定用例运行,可以指定函数或者方法 pytest.main(['-vs', './testcase/test_01.py::test_03'])
-
一般会安装allure-pytest插件,其提供了两个命令参数:--alluredir=:将allure格式的测试数据输出到指定目录,用于后续allure报告生成;
--clean-alluredir:在生成新的测试数据之前将清理指定的目录的数据。pytest.main(['-s', '-v', './testcase/', '--alluredir', './report/result/', '--clean-alluredir']) -
也可以带上-log-format和--log-date-format控制 pytest 内置日志系统(pytest logging)的输出格式的,不过一般是在pytest.ini中定义
pythonpytest.main(['-s', '-v', './api_case/test01.py', '--alluredir', './report/result/', '--clean-alluredir','--log-format','%(asctime)s.%(msecs)03d [%( filename)s:%(lineno)s] [%(levelname)5s] %(message)s','--log-date-format','%Y-%m-%d %H:%M:%S'])
3.2、命令行方式执行
参数与pytest.main()一致。
python
py.test.exe -vs .\testcase\test_01.py::Test_pytest::test_03 #windows下也可以直接用py.test.exe
pytest -vs --alluredir=/var/lib/jenkins/workspace/test/result --clean-alluredir testcase/
3.3、读取pytest.py配置文件运行
pytest.ini是一个手动创建的配置文件,用于读取整个项目的配置信息,pytest将按此配置文件中指定的方式去运行,并可以改变pytest的默认行为。pytest.ini配置文件存放在项目的根目录,文件名称固定不可修改,配置完后通过执行pytest调用,pytest.main()的部分参数也是会生效的。文件内容如下:
python
[pytest]
;addopts = -vs --html ./report/report.html
#排除测试用例的路径
norecursedirs =./testcase01
#测试用例的路径
testpaths = ./testcase
#模块名的规则
python_files = test_*.py
#类名的规则
python_classes = Test*
#方法名的规则
python_functions = test
#标志规则,默认是false标记@pytest.mark.xfail的用例会根据配置来设置结果
xfail_strict=true or false
#日志文件格式 时间+文件名+行数+日志等级+日志信息
log_format = %(asctime) s [%(filename) s:%(lineno)-4s] [%(levelname) 5s] %(message) s
#asctime的时间格式,%Y-%m-%d %H:%M:%S是到秒;%Y-%m-%d %H:%M:%S.%f是到微秒.但是在linux环境可能不生效直接原样输出为2025-11-14 13:27:05.%f,未找到原因。如果想输出ms,可以如下设置log_format = %(asctime)s.%(msecs)03d
log_date_format = %Y-%m-%d %H:%M:%S.%f
#首先在pytest.ini中定义分组名称,然后在用例中用@pytest.mark.smoke或@pytest.mark.test标记分组,用pytest -m "smoke or test"执行对应的分组用例
markers=smoke
get
first
last
run
norecursedirs 和testpaths 说明
-
当两者有冲突时,比如二者配置的一样,testpaths优先,也就是执行testpaths下的所有用例
-
testpaths包含norecursedirs,执行testpaths下除了norecursedirs的用例
-
norecursedirs包含testpaths,不执行任何用例,并给出警告
四、执行顺序
默认情况下,pytest测试用例的执行顺序是按先外层后内层(目录下的文件),再根据编写的顺序升序执行。
如果想自定义pytest测试用例的执行顺序,可以通过多种方式实现,常用的方法有:
-
利用pytest_ordering插件,通过装饰器@pytest.mark.run(order=1)来进行控制,数字越小,越前执行。
-
在测试方法上加装饰器@pytest.mark.first 表示第一个执行和@pytest.mark.last表示最后一个执行。优先级最高
五、用例跳过--skip
- 有条件跳过(在用例前面加上@pytest.mark.skipif(age>=18,reason = '已成年'))
- 无条件跳过(在用例前面加上@pytest.mark.skip)
六、标记失败用例-xfail
适用于用例写了但是代码没有实现,或者已知bug
使用方法:@pytest.mark.xfail(condition, reason="xxx" ) condition为预期失败的条件
1、assert成功。结果为xpass
python
@pytest.mark.xfail
def test_case1():
print("代码开发中")
assert 1==1
2、assert失败。结果为xfail
python
@pytest.mark.xfail
def test_case1():
print("代码开发中")
assert 1==2
3、condition为true,才会执行xfail判断。assert成功,结果为xpass;assert成功。结果为xfail
python
@pytest.mark.xfail(1==1, reason="代码开发中")
def test_case1():
print("代码开发中")
assert 1==1
4、condition为false,不执行xfail判断。直接执行用例
python
@pytest.mark.xfail(1==2, reason="代码开发中")
def test_case1():
print("代码开发中")
assert 1==1
5、将不符合预期的成功XPASS标记为失败,使用strict参数。
strict为关键字参数,默认值为False。
当strict=False时,如果用例执行失败,则结果标记为XFAIL,表示符合预期的失败。如果用例执行成功,则结果标记为XPASS,表示不符合预期的成功。
当strict=True时,如果用例执行成功,则结果将标记为FAILED,而不再标记为XPASS。
我们也可以在pytest.ini文件中配置,代码如下:
python
[pytest]
xfail_strict = ture
如果用例的失败不是因为所期望的异常导致的,pytest将会把测试结果标记为FAILED。
七、标记参数化-parametrize
https://blog.csdn.net/qishuzdh/article/details/124808459
参数化使用场景主要是针对同样的用例步骤,输入不同数据,结合参数实现数据驱动的测试
7.1、parametrize基础用法
python
@pytest.mark.parametrize(argnames, argvalues, ids=None, scope=None)
argnames(必选):字符串类型,指定测试函数的参数名列表(多个参数用逗号分隔)argvalues(必选):可迭代对象(如列表、元组、生成器等),提供多组测试数据。每组数据对应一个测试用例,长度必须与argnames中的参数数量一致。ids(可选):为每组测试数据生成自定义的测试用例名称(默认是数据的内存地址,可读性差)scope(可选):指定参数化的作用域,默认是"function"(每个测试函数独立参数化)。其他选项"class"/"module"/"session"- indirect:默认情况下,
@pytest.mark.parametrize的参数化数据是直接传递 给测试函数的。而indirect的作用是:将指定的参数名标记为"间接参数" ,这些参数的值不会直接传入测试函数,而是作为 fixture 的输入参数,调用对应的 fixture 并获取其返回值,最终将 fixture 的返回值传递给测试函数。
python
pytest.mark.parametrize的使用。https://blog.csdn.net/qishuzdh/article/details/124808459
# 1、一个参数一个值
@pytest.mark.parametrize("input", ["输入值"])
#2、一个参数多个值
@pytest.mark.parametrize("input", ["输入值1", "输入值2", "输入值3", "输入值4", "输入值5"])
# 3、多个参数多个值
@pytest.mark.parametrize("userName,passWord",[("xiaqiang", "123456"), ("rose", "123456"), ("jone", "123456"), ("Alix", "123456")])
# 4、多个参数混个使用
data1 = [1, 2]
data2 = ["python", "java"]
data3 = ["暴", "躁", "测", "试", "君"]
@pytest.mark.parametrize("a", data1)
@pytest.mark.parametrize("b", data2)
@pytest.mark.parametrize("c", data3)
# 5、参数化,传入字典数据
json=({"username":"alex","password":"123456"},{"username":"rongrong","password":"123456"})
@pytest.mark.parametrize('json', json)
# 6、参数化集合标记的使用
@pytest.mark.parametrize("user,pwd",[("xiaoqiang", "123456"), ("rose", "123456"),pytest.param("jone", "123456", marks=pytest.mark.xfail),pytest.param("Alex", "123456", marks=pytest.mark.skip)])
indirect的语法与取值
indirect可以是以下三种类型之一:
-
布尔值(
True/False):indirect=True:所有参数化参数(即argnames中声明的参数)都会被视为间接参数,强制通过 fixture 传递。indirect=False(默认值):所有参数直接传递给测试函数,不经过 fixture。
-
字符串列表:
指定哪些参数名需要作为间接参数(通过 fixture 传递),其他参数直接传递。例如
indirect=["browser", "env"]表示只有browser和env会触发 fixture 调用,其他参数直接传入测试函数。python# 定义两个 fixture,分别处理浏览器和环境 @pytest.fixture def browser(request): return f"{request.param}_instance" # 返回模拟实例 @pytest.fixture def env(request): return f"{request.param}_env" # 返回模拟环境 # 参数化测试,indirect=["browser", "env"] 表示两个参数都通过 fixture 传递 @pytest.mark.parametrize( "browser, env, test_case", [ ("chrome", "dev", "case1"), ("firefox", "staging", "case2"), ("edge", "prod", "case3") ], indirect=["browser", "env"] # browser 和 env 都走 fixture ) def test_multi_indirect(browser, env, test_case): print(f"测试用例 {test_case}: 在 {env} 环境使用 {browser} 执行")
7.2、parametrize通过读取excel的数据来实现数据驱动
python
import openpyxl
def get_data_excel():
wb = openpyxl.load_workbook(r"C:\Users\liyue03\Desktop\test\data\user_password.xlsx")
sheet = wb.worksheets[0]
list2 = []
for i in sheet.values:
list2.append(i)
else:
return list2
python
@pytest.mark.parametrize('username,password', get_data_excel())
def test_parm_excel(username, password):
print(f"姓名:{username}")
print(f"姓名:{password}")
7.3、parametrize通过读取csv的数据来实现数据驱动
python
import csv
def get_data_csv():
list1 = []
c1 = csv.reader(open(r"C:\Users\liyue03\Desktop\test\data\data.csv", encoding="UTF-8"))
for i in c1:
list1.append(i)
else:
return list1
python
@pytest.mark.parametrize('username,password', get_data_csv())
def test_parm_csv(username, password):
print(f"姓名:{username}")
print(f"姓名:{password}")
八、pytest前后置
前置setup和后置teardown来处理测试用例执行前的准备工作(浏览器驱动实例化,数据库连接等)以及执行后的处理工作(清理数据,关闭浏览器驱动,关闭数据库连接等),pytest中存在两种前后置方式:1、setup/teardown;2、fixture。
8.1、setup/teardown
https://www.cnblogs.com/trystudy/p/17025701.html
pytest提供了以下5个前置后置方法:
- setup、teardown:每条用例都会执行,既可以在类中使用,也可以在类外使用--但是新版的pytest不支持setup和teardown
- setup_class、teardown_class:类中的测试用例执行前后只执行一次
- setup_method、teardown_method:类中的每条测试用例执行前后都执行一次
- setup_function、teardown_function:类外的每条测试用例执行前后都执行一次
- setup_module、teardown_module:类外的测试用例执行前后只执行一次
执行顺序:
类中前置执行顺序:setup_class > setup_method (后置执行顺序则相反)
类外前置执行顺序:setup_module > setup_function (后置执行顺序则相反)
python
def setup_function():
print('setup_function前置执行')
def teardown_function():
print('teardown_function后置执行')
def setup_module():
print('setup_module前置执行')
def teardown_module():
print('teardown_module后置执行')
class Test_04:
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 test_01(self):
print("-"*5,"类内用例01","-"*5)
def test_02(self):
print("-"*5,'类内用例02',"-"*5)
def test_case1():
print("-"*5,"类外用例01","-"*5)
assert 1 == 1
def test_case2():
print("-"*5,"类外用例02","-"*5)
assert 1 == 1
执行结果:
testcase/test_01.py::Test_04::test_01 setup_module前置执行
setup_class前置执行
setup_method前置执行
----- 类内用例01 -----
PASSEDteardown_method后置执行
testcase/test_01.py::Test_04::test_02 setup_method前置执行
----- 类内用例02 -----
PASSEDteardown_method后置执行
teardown_class后置执行
testcase/test_01.py::test_case1 setup_function前置执行
----- 类外用例01 -----
PASSEDteardown_function后置执行
testcase/test_01.py::test_case2 setup_function前置执行
----- 类外用例02 -----
PASSEDteardown_function后置执行
teardown_module后置执行
8.2、fixture 机制
8.2.1、fixture优点
fixture相对于pytest中的setup和teardown来说有以下几点优势:
- fixure命名更加灵活,局限性比较小;
- conftest.py 配置里面可以实现数据共享,不需要import就能自动找到一些配置;
- scope="session"可以实现多个.py跨文件使用一个session来完成多个用例。
8.2.2、fixture语法
语法如下:
python
fixture(callable_or_scope=None, *args, scope="function", params=None, autouse=False, ids=None, name=None)
- scope:fixture的作用域,默认为function;scope参数可以是session, module,class,function
- session 会话级别:是多个文件调用一次,可以跨.py文件调用,每个.py文件就是module;
- module 模块级别:模块里所有的用例执行前执行一次module级别的fixture;
- class 类级别 :每个类执行前都会执行一次class级别的fixture;
- function 函数级别:每个测试用例执行前都会执行一次function级别的fixture。
- parmas:允许 fixture 为测试提供多组数据,每个参数值都会生成一个独立的测试用例。一般用于参数化用例,request参数可以调用parmas传入的参数。
- autouse:默认:False,需要用例手动调用该fixture;如果是True,所有作用域内的测试用例都会自动调用该fixture;
- name:装饰器的名称,同一模块的fixture相互调用建议写不同的name。
8.2.3、定义fixture
定义fixture,在函数上添加@pytest.fixture即可。
python
@pytest.fixture()
def fixture_demo():
print("这是fixture")
8.2.4、如何区分前后置
在pytest中,用yield区分前后置,即yield前面代码为前置,后面代码为后置。
python
from selenium import webdriver
@pytest.fixture()
def open_browser_init():
# 前置
driver = webdriver.Chrome()
driver.get("https://www.baidu.com")
yield driver
# 后置
driver.quit()
8.2.5、 fixture调用
https://blog.csdn.net/weixin_67553250/article/details/140805658
一共存在三种调用方式:
- 将fixture名称作为参数传入测试用例,如果fixture有返回值,那么测试用例将会接收返回值。
- pytest装饰器调用fixture。@pytest.mark.usefixtures("XXX")
- autouse调用fixture。
python
@pytest.fixture()
def fixture_test():
print("pytest fixture 前置")
yield
print("pytest fixture 后置")
@pytest.fixture(autouse=True)
def fixture_test_auto():
print("pytest fixture autouse 开启前置")
yield
print("pytest fixture autouse 开启后置")
def test_01(fixture_test):
print("打印test01_def")
@pytest.mark.usefixtures("fixture_test")
def test_02(fixture_test):
print("打印test02_def")
执行结果:
testcase/test_01.py::test_01 pytest fixture autouse 开启前置
pytest fixture 前置
打印test01_def
PASSEDpytest fixture 后置
pytest fixture autouse 开启后置
testcase/test_01.py::test_02 pytest fixture autouse 开启前置
pytest fixture 前置
打印test02_def
PASSEDpytest fixture 后置
pytest fixture autouse 开启后置
parmas和request的使用
params参数的主要作用是为 fixture 提供参数化能力,让同一个 fixture 可以根据不同的参数值生成多个测试用例。request是 pytest fixture 中的一个特殊参数,它是一个 FixtureRequest 对象,提供了访问测试上下文信息的能力。最主要的使用当使用params参数定义 fixture 时,request.param用于获取当前的参数值。
python
import pytest
@pytest.fixture(params=["apple", "banana", "cherry"])
def detailed_fixture(request):
"""展示 request 对象的所有重要属性"""
print("\n" + "=" * 50)
print("REQUEST 对象属性详情:")
print("=" * 50)
# 1. 参数相关属性
print(f"request.param: {request.param}") # 当前参数值
print(f"request.param_index: {request.param_index}") # 参数索引
# 2. Fixture 相关属性
print(f"request.fixturename: {request.fixturename}") # fixture名称
print(f"request.scope: {request.scope}") # 作用域
# 3. 测试上下文属性
print(f"request.function: {request.function.__name__ if hasattr(request, 'function') else 'N/A'}")
print(f"request.cls: {request.cls}") # 测试类(如果有)
print(f"request.module: {request.module.__name__ if hasattr(request, 'module') else 'N/A'}")
# 4. 会话和配置
print(f"request.config: {type(request.config)}") # 配置对象
return {
"param": request.param,
"index": request.param_index,
"fixture_name": request.fixturename,
"scope": request.scope
}
def test_request_attributes(detailed_fixture):
"""测试 request 对象的属性"""
print(f"测试接收到的fixture数据: {detailed_fixture}")
其中一个参数的输出结果如下:
testcase/test_request.py::test_request_attributes[apple]
==================================================
REQUEST 对象属性详情:
==================================================
request.param: apple
request.param_index: 0
request.fixturename: detailed_fixture
request.scope: function
request.function: test_request_attributes
request.cls: None
request.module: testcase.test_request
request.config: <class '_pytest.config.Config'>
测试接收到的fixture数据: {'param': 'apple', 'index': 0, 'fixture_name': 'detailed_fixture', 'scope': 'function'}
PASSED
8.2.6、conftest.py的作用范围
conftest.py文件名称时固定的,pytest会自动识别该文件。一个工程下可以建多个conftest.py的文件,一般在工程根目录下设置的conftest文件起到全局作用。在不同子目录下也可以放conftest.py的文件,作用范围只能在改层级以及以下目录生效。可以将fixture全部放到conftest.py。
conftest.py示例
python
import pytest
@pytest.fixture(scope='session', autouse=True)
def login():
print('----准备登录----')