pytest学习
文章目录
- pytest学习
-
- [1. 用例结果](#1. 用例结果)
- [2. 用例发现规则](#2. 用例发现规则)
- [3. 用例内容规则](#3. 用例内容规则)
- [4. 小测试](#4. 小测试)
- [5. 配置框架](#5. 配置框架)
- [6. 标记mark](#6. 标记mark)
-
- [1. 用户自定义标记](#1. 用户自定义标记)
- [2. 框架内置标记](#2. 框架内置标记)
- [7. 数据驱动测试参数](#7. 数据驱动测试参数)
- [8. 夹具fixture](#8. 夹具fixture)
-
- [1. 创建使用fixture](#1. 创建使用fixture)
- [2. 高级用法](#2. 高级用法)
- [9. 插件](#9. 插件)
-
- [1. 插件管理](#1. 插件管理)
- [2. 常用第三方插件](#2. 常用第三方插件)
- [10. 企业级测试报告](#10. 企业级测试报告)
- [11. Web自动化测试](#11. Web自动化测试)
- [12. 测试框架要封装什么](#12. 测试框架要封装什么)
- [13. YAML文件格式](#13. YAML文件格式)
-
- [13.1 安装yaml文件](#13.1 安装yaml文件)
- [13.2 编写yaml文件](#13.2 编写yaml文件)
- [13.3 加载yaml文件](#13.3 加载yaml文件)
- [14. 接口测试用例](#14. 接口测试用例)
- 参考
常见测试框架
java:junit,testng
python:unittest,pytest
pip install pytest -U
pytest有三种启动方式:
- 命令
- 代码
- 鼠标
1. 用例结果
1.执行环境:版本、根目录、用例数量
2.执行过程:文件名称、用例结果、执行进度
3.失败详情:用例内容、断言提示
4.整体摘要:结果情况、结果数量、花费时间
缩写 | 单词 | 含义 |
---|---|---|
. | passed | 通过 |
F | failed | 失败(用例执行时报错) |
E | error | 出错(fixture执行报错) |
s | skipped | 跳过 |
X | xpassed | 预期外的通过(不符合预期) |
x | xfailed | 预期内的失败(符合预期) |
2. 用例发现规则
测试框架在识别、加载用例的过程,称之为:用例发现
pytest的用例发现步骤:
1.遍历所有的目录,例外:venv
,.
开头的目录
2.打开python文件,test_
开头或者_test
结尾
3.遍历所有的Test开头类
4.收集所有的test_
开头的函数或者方法
3. 用例内容规则
pytest 8.4 增加了一个强制要求
pytest对用例的要求:
1.可调用的(函数、方法、类、对象)
2.名字test_
开头
3.没有参数(参数有另外含义)
4.没有返回值(默认为None)
4. 小测试
python
import pytest
pytest.main()
test_add.py
python
import pytest
def add(a,b):
return a+b
def test_add():
res=add(1,3)
assert res==4
def test_add_():
res=add("1","3")
assert res=="13"
class TestAdd:
def test_int(self):
res=add(1,3)
assert res==4
def test_str(self):
res=add("1","3")
assert res=="13"
def test_list(self):
res=add([1,2,3],4)
assert res==[1,2,3]
bash
============================= test session starts =============================
platform win32 -- Python 3.13.7, pytest-8.4.2, pluggy-1.6.0
rootdir: C:\Users\21609\PycharmProjects\PythonProject1\pytest
plugins: anyio-4.10.0
collected 5 items
test_add.py ....F [100%]
================================== FAILURES ===================================
______________________________ TestAdd.test_list ______________________________
self = <test_add.TestAdd object at 0x0000025FC14A6D70>
def test_list(self):
> res=add([1,2,3],4)
^^^^^^^^^^^^^^
test_add.py:20:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
a = [1, 2, 3], b = 4
def add(a,b):
> return a+b
^^^
E TypeError: can only concatenate list (not "int") to list
test_add.py:2: TypeError
=========================== short test summary info ===========================
FAILED test_add.py::TestAdd::test_list - TypeError: can only concatenate list...
========================= 1 failed, 4 passed in 0.10s =========================
5. 配置框架
配置可以改变 pytest 默认的规则:
- 命令参数
- ini文件文件
查看所有的配置方式
pytest -h
有哪些配置
- -开头:命令行参数
- 小写字母开头:ini配置
- 大写字母开头:环境变量
配置文件:pytest.ini
常用参数:
-v:增加用例详细内容
-s:在用例中正常的使用输入输出
-x:快速退出,当遇到失败的用例则全部停止执行
-m:用例筛选(pytest -m web)
6. 标记mark
标记可以让用例与众不同,进而可以让用例被区别对待。
1. 用户自定义标记
用户自定义标记只能实现用例筛选。
步骤:
- 先注册
- 再标记
- 后筛选
pytest.ini文件中增加内容:
ini
[pytest]
markers =
api: 接口
web: UI测试
Ut: 单元测试
login: 登录相关
pay: 支付相关
pytest --markers

修改test_add.py
python
class TestAdd:
@pytest.mark.api
def test_int(self):
res=add(1,3)
assert res==4
@pytest.mark.web
def test_str(self):
res=add("1","3")
assert res=="13"
@pytest.mark.UI
def test_list(self):
res=add([1,2,3],4)
assert res==[1,2,3]
只执行web用例
pytest -m web

2. 框架内置标记
框架内置标记可以为用例增加特殊执行效果。
和用户自定义标记区别:
-
不需注册,可以直接使用
-
不仅可以筛选,还可以增加特殊效果
-
不同的标记,增加不同的特殊效果
-
skip:无条件跳过
-
skipif:有条件跳过
-
xfail:预期失败
-
parametrize:参数化
-
usefixtures:使用fixtures
-
修改test_add.py
python
class TestAdd:
@pytest.mark.skip
@pytest.mark.api
def test_int(self):
res=add(1,3)
assert res==4
@pytest.mark.skipif(1==2,reason="跳过了")
@pytest.mark.web
def test_str(self):
res=add("1","3")
assert res=="13"
@pytest.mark.xfail()
@pytest.mark.UI
def test_list(self):
res=add([1,2,3],[4])
assert res==[1,2,3,4]
@pytest.mark.xfail()
@pytest.mark.UI
def test_list_(self):
res = add([1, 2, 3], [4])
assert res != [1, 2, 3,4]

7. 数据驱动测试参数
数据驱动测试=参数化测试+数据文件
根据数据文件的内容,动态决定用例的数量、内容
新建data.csv
a,b,c
1,1,2
2,3,5
3,3,6
4,4,7
pytest.ini添加ddt: 数据驱动测试
修改test_add.py
python
from baifan import readCSV
class TestAdd:
#其他不变
@pytest.mark.ddt
@pytest.mark.parametrize("a,b,c",readCSV("data.csv"))
def test_ddt(self,a,b,c):
res=add(int(a),int(b))
assert res==c
python
import csv
def readCSV(csvFile):
with open(csvFile, newline='') as f:
reader = csv.reader(f)
return list(reader)[1:]
8. 夹具fixture
夹具:在用例执行之前、执行之后,自动运行代码
场景:
-
之前:加密参数/之后:解密结果
-
之前:启动浏览器/之后:关闭浏览器
-
之前:注册、登录账号/之后:删除账号
1. 创建使用fixture
创建
- 创建函数
- 添加装饰器
- 添加yield关键字
使用
方法1:在用例的参数列表中,加入fixture名字即可
方法2:给用例加上usefixtures标记
新建test_1.py
python
import datetime
import pytest
@pytest.fixture
def app():
print(datetime.datetime.now(),"用例开始执行")
# 前置操作
yield
# 后置操作
print(datetime.datetime.now(),"用例执行完毕")
def test_1(app):
pass
@pytest.mark.usefixtures("app")
def test_2():
pass
pytest -vs

2. 高级用法
-
自动使用(所有用例都会使用该fixture)(@pytest.fixture(autouse=True))
-
依赖使用(fixture作为参数,一个fixture作为另一个fixture参数)
- linux:使用linux进行编译
- git:使用git进行版本控制
- fixture:使用fixture进行前后置自动操作
-
返回内容:接口自动化封装:接口关联
-
范围共享
-
默认范围:function
-
全局范围:session
- 使用conftest.py(跨文件传递命名空间)
-
test_1.py
python
import datetime
import pytest
@pytest.fixture
def ff():
print("这也是fixture")
@pytest.fixture(autouse=True)
def app(ff):
print(datetime.datetime.now(),"用例开始执行")
# 前置操作
yield 123
# 后置操作
print(datetime.datetime.now(),"用例执行完毕")
def test_1():
pass
def test_2():
pass
def test_3(app):
print("接收到传递数据",app)

test_1.py
python
def test_1():
pass
def test_2():
pass
test_2.py
python
def test_3(app):
print("接收到传递数据",app)
新建C:\Users\21609\PycharmProjects\PythonProject1\conftest.py
python
import datetime
import pytest
@pytest.fixture(scope="session")
def ff():
print("这也是fixture")
@pytest.fixture(autouse=True, scope="session")
def app(ff):
print(datetime.datetime.now(),"用例开始执行")
# 前置操作
yield 123
# 后置操作
print(datetime.datetime.now(),"用例执行完毕")
可以看到跨文件传递命名空间和范围共享的效果

9. 插件
1. 插件管理
pytest插件生态是pytest特别的优势之处。
插件分成两类:
- 不需要安装:内置插件
- 需要安装:第三方插件
插件的启用管理:
- 启用:-p abc
- 禁用:-p no:abc
插件使用方式:
- 参数
- 配置文件
- fixture
- mark
2. 常用第三方插件
pytest 有1400+插件:https://docs.pytest.org/en/stable/reference/plugin_list.html
pytest-html
用途:生成HTML测试报告
安装:
pip install pytest-html
使用(参数):
pytest --html=report.html --self-contained-html
或者在pytest.ini里面加上
(注意pytest.ini要移动到C:\Users\21609\PycharmProjects\PythonProject1)
addopts = --html=report.html --self-contained-html
然后
pytest -sv

pytest-xdist
用途:分布式执行(多进程)
安装:
pip install pytest-xdist
使用:
-n N
只有在任务本身耗时较长,超出调用成本很多的时候,才有意义。
分布式执行,有并发问题,资源竞争,乱序。
pytest-rerunfailures
用途:用例失败之后,重新执行
安装:
pip install pytest-rerunfailures
使用:
--reruns 5 --reruns-delay 1
pytest-result-log
用途:把用例的执行结果记录到日志文件中
安装:
pip install pytest-result-log
使用:
ini
log_file =./logs/pytest.log
log_file_level= info
log_file_format =%(levelname)-8s %(asctime)s [%(name)s:%(lineno)s]:%(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S
;记录用例执行结果
result_log_enable= 1
;记录用例分制线
result_log_separator= 1
;分制线等级
result_log_level_separator= warning
;异常信息等级
result_log_level_verbose =info
10. 企业级测试报告
allure是一个测试报告框架。
下载allure
https://github.com/allure-framework/allure2/releases
将C:\Users\21609\Downloads\allure-2.35.1\bin
添加到环境变量
安装
pip install allure-pytest
配置
修改pytest.ini
addopts = --alluredir=temps --clean-alluredir
生成报告(命令行,也可以通过python代码来启动)
pytest
allure generate -o report -c temps

allure支持对用例进行分组和关联(敏捷开发术语)
import allure
@allure.epic 史诗 项目
@allure.feature 主题 模块
@allure.story 故事 功能
@allure.title 标题 用例
修改test_1.py
import allure
@allure.epic("a")
@allure.feature("b")
@allure.story("c")
@allure.title("d")
def test_1():
pass
def test_2():
pass

11. Web自动化测试
pytest 仅进行用例管理,不会控制浏览器,需要借助新的工具:selenium。
- 只了解selenium
- 搜索关于selenium的pytest插件
自己实现类似selenium的功能:
pip install pytest-selenium
关闭杀毒软件
修改pytest.ini对应部分
addopts = --alluredir=temps --clean-alluredir -m web --driver chrome -vs
test_1.py
python
import allure
import pytest
import time
@pytest.mark.web
def test_1(selenium):
selenium.get("https://www.baidu.com")
print(selenium.title)
def test_2():
pass

12. 测试框架要封装什么
封装:
- 隐藏细节
- 增加功能
- 优化功能
接口自动化封装:
- 使用yaml作为用例,降低自动化门槛
- 自动请求接口、断言接口
- 自动在日志记录HTTP报文
- 自动生成allure测试报告
13. YAML文件格式
一句话:YAML完全兼容JSON格式,并且支持Python相似写法
重点:
- YAML完全兼容JSON
- 是数据格式,不是编程语言
- 像Python一样容易编辑和阅读
13.1 安装yaml文件
pip install pyyaml
13.2 编写yaml文件
-
#作为注释符号
-
缩进:使用2个空格
-
成员表示
-
-
表示列表成员 -
:
表示字典成员 -
null
表示空值 -
"666"
表示字符串 -
[1,2,3,]
表示列表 -
{"a":1,"b":2}
表示字典
-
-
兜底:完全兼容JSON
13.3 加载yaml文件
python
import yaml
def Load_yaml():
f=open("baifan.yaml",encoding="utf-8")#打开文件
s=f.read()#读取文件内容
data =yaml.safe_load(s)
print(data)
f.close()
Load_yaml()
#{'字典的键': [1, -2, 3.3], '字符串': ['123', '456', 'aaaa']}
baifan.yaml
yaml
字典的键:
- 1
- -2
- 3.3
字符串:
- '123'
- "456"
- aaaa
14. 接口测试用例
14.1 请求接口
外部工具:requests
从HTTP协议抓包角度,请求由三部分组成:
- 行:方法+地址(必填)
- 头:请求头(键值对)
- 体:参数内容
14.2 断言响应
- 响应里有什么
- 响应如何断言
从HTTP协议抓包角度,响应由三部分组成:
- 行:状态码
- 头:响应头(键值对)
- 体:响应内容
14.3 变量的提取
基本原则:
- JSON:JSONPATH
- HTML:XPATH
- 字符串:RE
14.4 框架的落地封装
文件结构,日志,内容
进一步完善:
- YAML用例测试文件上传?
- YAML用例进行数据去掉测试?
- YAML用例进行自定义的断言?
- YAML用例进行数据库查询?
两个方向:
- 自动化测试工程师
- 测试开发工程师
test_api.yaml
yaml
name : 登陆成功用例
steps:
- request : #发送请求
method : POST
url : http://116.62.63.211/shop/api.php?application=app&application_client_type=weixin
params :
s : user/login
json : {
"accounts" : "beifan_1105",
"pwd" : "beifan_1105",
"type" : "username"
}
- response : #断言响应
status_code : 200
json :
code : 0
msg : 登录成功
data :
username : beifan_1105
- extract : #提取变量
token : [ json , $.data.token ]
test_yaml.py
python
import json
import re
from unittest import case
import jsonpath
import requests
import responses_validator
from responses_validator import validator
import allure
import yaml
import pytest
@pytest.mark.api
def test_yaml():
my_var={}
with open(r"C:\Users\21609\PycharmProjects\PythonProject1\pytest\test_api.yaml",encoding="utf-8") as f:
data=yaml.safe_load(f)#打开文件
allure.title(data['name'])
for step in data['steps']:
for k,v in step.items():
runner(k,v,my_var)
def extract(resp,attr_name,exp):
try:
resp.json=resp.json()
except:
resp.json={}
#attr_name为json
attr=json.loads(json.dumps(getattr(resp,attr_name)))
#下面的jsonpath需要字符串里面用双引号
#json.dumps将字典getattr(resp,attr_name)转为json格式字符串,并替换键或者值单引号字符串'name'为双引号字符串"name"
res=jsonpath.jsonpath(attr,exp)
print("res",res,resp.json)
return res[0]
def runner(k,v,my_var):
match k:
case "request":
my_var['resp']=requests.request(**v)
case "response":
responses_validator.validator(my_var['resp'],**v)#字典使用2个星号
case "extract": #变量提取
for var_name,var_exp in v.items():
value=extract(my_var['resp'],*var_exp)#列表使用一个星号
pytest.ini
ini
[pytest]
addopts = --alluredir=temps --clean-alluredir -m api -vs
markers =
api: 接口
web: UI测试
Ut: 单元测试
login: 登录相关
pay: 支付相关
ddt: 数据驱动测试
log_file =./logs/pytest.log
log_file_level= info
log_file_format =%(levelname)-8s %(asctime)s [%(name)s:%(lineno)s]:%(message)s
log_file_date_format = %Y-%m-%d %H:%M:%S
;记录用例执行结果
result_log_enable= 1
;记录用例分制线
result_log_separator= 1
;分制线等级
result_log_level_separator= warning
;异常信息等级
result_log_level_verbose =info
响应包如下
{'msg': '登录成功', 'code': 0, 'data': {'id': 23528, 'alipay_openid': '', 'weixin_openid': '', 'weixin_unionid': '', 'weixin_web_openid': '
', 'baidu_openid': '', 'toutiao_openid': '', 'qq_openid': '', 'qq_unionid': '', 'status': 0, 'token': '13d08c2af7fed8e841c0ebc1d7fe8441', 'username': 'beifan_1105', 'nickname': '11
', 'mobile': '', 'email': '', 'gender': 0, 'avatar': 'http://116.62.63.211/shop/public/static/upload/images/user_avatar/compr/2024/11/05/20241105204003902611684.png', 'province': '
', 'city': '', 'birthday': 1731168000, 'address': '', 'integral': 0, 'locking_integral': 0, 'referrer': 0, 'is_delete_time': 0, 'add_time': 1730808873, 'upd_time': 1760245502, 'add_time_text': '2024-11-05 20:14:33', 'upd_time_text': '2025-10-12 13:05:02', 'gender_text': '保密', 'birthday_text': '2024-11-10', 'mobile_security': '', 'email_security': '', 'user_name_view': 'beifan_1105', 'is_mandatory_bind_mobile': 0}}

参考
【B站讲的最好的Pytest自动化测试教程,自动化必备之Pytest测试框架训练营,只需一小时速成,学会直接上手实操!】 https://www.bilibili.com/video/BV1rDdHYCEUP/?p=4\&share_source=copy_web\&vd_source=aeb4f89b7c74bd892c2a834774309409