接口自动化测试框架pytest&allure&requests介绍
接口自动化测试框架,增加日志模块,删除数据模型层,修改request请求方法
一、pytest 常见的插件
pytest: 框架本身
pytest-html:生成HTML报告
pytest-xdist: 分布式执行
pytest-order:自定义用例执行顺序
pytest-rerunfailures:失败重跑
pytest-base_url:管理base_url
allure-pytest:生成allure的测试报告
二、pytest 框架功能
1发现测试用例:从多个py文件收集、加载测试用例
a遍历所有的目录(venv除外)
b遍历所有的py文件(名字是test_开头 或者 _test结尾)
c收集:test_开头的的函数 (重点:这个才是用例)
2执行测试用例:按照一定的顺序和条件进行执行
3判断测试结果:(断言)判断预期结果和实际的结果是否一致
4生成测试报告:用例数量、花费时间、失败原有、测试通过率
三、pytest常用的命令行参数:
●-h :显示帮助信息和参数、配置信息
●-v :增加结果的详细程度
●-q :减少结果的详细程度
●-s : 关闭IO捕获,input和print可以正常执行
●-k :根据用例名筛选用例
●-m :根据标签筛选用例
##插件提供参数
●-n: 使用多进程分布式执行[-n 0 不使用分布式,-n 2 两个进程,最少2个才能实现分布式]
●–reruns :失败重跑的次数 [pytest-rerunfailures:失败重跑插件提供]
●–html:生成HTML报告 [allure-pytest:生成allure的测试报告插件提供]
四、pytest用法实例
test_demo.py
def test_demo1():
print(“test_demo1”)
assert 1 == 2
def test_demo1_1():
assert 1 == 1
执行用例
执行测试用例
实际上是启动框架,框架自动的发现和执行用例
方式一: 命令行
pytest
方式二:主函数 run.py
import pytest
pytest.main()
执行结果查看
test_demo1.py F. 【test_demo1.py里有两个用例,一个成功,一个失败】
结果的缩写:
●. : 通过 (pass)
●F: 失败(failed)
●s :跳过(skip)
●x :预期外的通过(xpassed)
●X :预期内的失败(xfailed)
五、 -k 参数控制执行用例【根据方法名获取def方法名获取,可以只提供一部分然后根据提供名称去匹配,如果用例名字很相似,可能会选择多个】
(venv) (base) PS D:\PythonProject\ApiFramwor> pytest -s -v -k test_demo2
===================================================== test session starts =====================================================
platform win32 – Python 3.14.7, pytest-9.2.0, pluggy-1.7.0 – D:\PythonProject\ApiFramwor\venv\Scripts\python.exe
cachedir: .pytest_cache
metadata: {‘Python’: ‘3.14.7’, ‘Platform’: ‘Windows-10-10.0.18363-SP0’, ‘Packages’: {‘pytest’: ‘9.2.0’, ‘pluggy’: ‘1.7.0’}, ‘Plugins’: {‘allure-pytest’: ‘2.16
.2’, ‘base-url’: ‘2.1.0’, ‘html’: ‘4.2.0’, ‘metadata’: ‘3.1.1’, ‘order’: ‘1.5.0’, ‘rerunfailures’: ‘16.7’, ‘xdist’: ‘3.8.0’}, ‘JAVA_HOME’: ‘D:\jmeter-5.6.3\
jdk-21.0.11’, ‘Base URL’: ‘’}
rootdir: D:\PythonProject\ApiFramwor
configfile: pyproject.toml
plugins: allure-pytest-2.16.2, base-url-2.1.0, html-4.2.0, metadata-3.1.1, order-1.5.0, rerunfailures-16.7, xdist-3.8.0
collected 3 items / 2 deselected / 1 selected
找到了3个用例,2个为未选中,1个选中了
demo2_test.py::test_demo2 PASSED
======================================================
1 passed, 2 deselected in 0.02s ======================================================
六、配置文件(pytest.ini)
在配置文件中注册,在命令行中使用
1根目录中创建配置文件:pytest.ini
2配置文件中创建选择器:pytest
3按行写入配置内容
[pytest]
命令行参数
addopts = -vs
注册标记
markers =
smoke:冒烟测试
usermanager:用户管理
api:接口用例
ut:单元测试用例
七、 pytest标记:mark
用户的标记
先注册,后使用
要在配置文件中注册,在命令行参数中使用
import pytest
@pytest.mark.smoke
def test_baili():
assert 1 == 1
@pytest.mark.web
def test_a():
pass
@pytest.mark.web
def test_b():
pass
@pytest.mark.smoke
@pytest.mark.api
def test_c():
pass
pytest.ini
[pytest]
命令行参数
addopts = -vs -m smoke
八、插件的标记
使用order标记来控制用例 顺序:数字越小越靠前
import random
import pytest
import time
@pytest.mark.smoke
@pytest.mark.order(2)
def test_baili():
n = random.randint(1,5)
assert 1 == n
@pytest.mark.web
def test_a():
pass
@pytest.mark.web
def test_b():
pass
@pytest.mark.smoke
@pytest.mark.api
@pytest.mark.order(1)
def test_c():
pass
九、框架标记
pytest自带的标记,拥有不同的效果:
●skip:无条件跳过
●skipif:有条件跳过
●xfail:预期失败
●parametrize:参数化测试(后面讲数据驱动测试)
import pytest
def test_a():
assert 1 == 1
def test_b():
assert 1 == 2
@pytest.mark.skipif(1!=1, reason=“当1==1的时候跳过”)
def test_c():
assert 3 == 3
@pytest.mark.xfail # 预期失败
def test_d():
assert 1 == 1
@pytest.mark.xfail
def test_e():
assert 1 == 2
十、依赖文件requirements.txt
pytest
pytest-html
pytest-xdist
pytest-order
pytest-rerunfailures
pytest-base_url
allure-pytest
安装依赖
pip install -r requirements.txt
十一、pytest的固件风格:fixtures
创建方式:
是一个函数
使用装饰器 @pytest.fixture
使用生成器关键字:yield
11.1. fixture源码
def fixture(
fixture_function: FixtureFunction | None = None,
*,
scope: _ScopeName | Callable[[str, Config], _ScopeName] = “function”,
params: Iterable[object] | None = None,
autouse: bool = False,
ids: Sequence[object | None] | Callable[[Any], object | None] | None = None,
name: str | None = None,
) -> FixtureFunctionMarker | FixtureFunctionDefinition:
11.2. fixture本质
●fixture_function:被装饰的函数
11.3. fixture 共享范围【实现前置和后置操作,谨慎使用参数化】
●scope:共享范围(不重复执行,即使在一个class中重复引用,也只会执行一次)
○function:函数级别
○class:类 (引用后,再整个类里都会执行,即使第一个用例引用了,第二个没有引用,也会再第二个用例执行后才追执行yield后的内容)
○module:模块
○package:包
○session:全局
import pytest
import time
@pytest.fixture()
def start_function():
#前置处理
print(“用例开始执行”,time.time())
yield
#后置操作
print(“用例执行完成”,time.time())
@pytest.fixture(scope=“class”)
def start_class():
print(“用例类开始执行”,time.time())
yield
print(“用例类执行完成”,time.time())
@pytest.fixture(scope=“session”)
def start_session():
print(“session全局fixture开始执行”,time.time())
yield
print(“session全局fixture执行完成”,time.time())
class Test:
def test_a(self,start_function,start_class,start_session):
assert 1 == 1
def test_b(self,start_session):
assert 1 == 2
11.4. fixtures的参数化
params
谨慎使用fixture参数化,因为它让用例被动也进行了参数化
import time
import pytest
@pytest.fixture(params=[1,2,3])
def beifan():
# 前置
print(‘用例开始执行’, time.time())
yield
# 后置
print('用例结束执行', time.time())
def test_abc(beifan):
print(123)
11.5. fixture自动使用【使用 autouse=True注释的fixture会自动引用,自动执行,如果fixture不在使用了,仅需删除就可以了,不会对用例产生影响】
autouse
import pytest
import time
#autouse=True的fixture会自动执行,不需要引用自动执行
@pytest.fixture(scope=“session”,autouse=True)
def fixture_session():
print(“测试用例开始执行”,time.time())
yield
print(“测试用例执行完成”,time.time())
class Test:
def test_a(self):
assert 1 == 1
def test_b(self):
assert 1 == 2
11.6. fixture 动态名称【应用场景:当有多个fixture时,通过name参数实现使用哪个fixture,使用哪个,给哪个fixture添加name参数,name名字根据用例里定义的引用fixture来调整实现引用,实现多fixtrue间共存,在旧代码重构时,fixture被多个用例引用,不想调整原来的用例引用时,通过name调整新fixture的名称实现】
●name
import pytest
import time
@pytest.fixture(scope=“session”)
def fixture_session():
print(“测试用例开始执行”,time.time())
yield
print(“测试用例执行完成”,time.time())
@pytest.fixture(scope=“function”,name=“fixture_demo”)
def fixture_function():
print(“start caas”,time.time())
class Test:
def test_a(self,fixture_demo):
assert 1 == 1
def test_b(self):
assert 1 == 2
十二、conftest 统一管理fixture
注意:
1 conftest.py 文件名是固定的,不能修改、不能写错
2 conftest.py 中的fixtrue会自动的被pytest加载,自动被任意一个用例使用
3 conftest.py 可以在每个目录中创建,用例按照就近原则来寻找fixture(当前文件、同目录conftest、上级目录conftest、上上级目录conftest…)
简单起见:所有的fixtures 定义在 根目录的conftest.py中【方便管理】
fixture引用时,现在当前文件中找,如果文件中没有在到当前目录找,如果目录也没有回到根目录中找,按照就近原则引用fixture
十三、插件的fixture【pytest-base-url的插件,提供了一个 base_url的fixture,实现base_url的灵活管理】
pytest-base-url的插件,提供了一个 base_url的fixture,内容可以灵活设置,用法相关简单和固定【使用过程中如果地址更新了,仅需要调整 pytest.ini里 base_url 定义即可】
def test_api(base_url):
# 使用fixture
print(base_url)
Pytest.ini
[pytest]
命令行参数
addopts = -vs
markers =
smoke:冒烟测试
usermanager:用户管理
api:接口用例
web:UI用例
ut:单元测试用例
为fixture传值
base_url = https://www.qq.com
从本质上来看,pytest-base-url的插件实现了一种效果:把配置文件的内容,动态的传递到用例中
十四、pytest的运行过程
1 run.py:启动框架
2 pytest.ini: 配置文件,参数、标记、配置
3 conftest.py:加载fixture、和hook
4 test_*.py:加载测试用例文件
十五、Allure测试报告
15.1.生成allure执行数据( 需要安装 allure-pytest 插件)
[pytest]
命令行参数
addopts = -vs ‐‐alluredir=./temps ‐‐clean‐alluredir
15.2.生成allrue测试报告 (allure)
os.system(“allure generate ./temps -o ./reports --clean”) # 根据数据 生成报告
allure的常用命令
allure generate # 生成报告
allure serve # 展示报告 allure serve ./temps
allure open # 打开已存在的报告 allure open ./reports/
allure plugin # 展示插件
serve = generate + open
15.3内容定制
本质:对执行结果进行标注和分组
方式:装饰器添加不同的标注内容
内容:
●epic:史诗,项目名称
●feature:特性,模块名称
●story: 故事,接口名称
●title:标题,用例名称
import allure
@allure.epic(“码尚教育自动化测是项目”)
@allure.feature(“商品管理模块”)
@allure.story(“商品管理接口”)
@allure.title(“商品创建用例-成功”)
def test_api():
pass
@allure.epic(“码尚教育自动化测是项目”)
@allure.feature(“商品管理模块”)
@allure.story(“商品管理UI”)
@allure.title(“商品创建用例-成功”)
def test_web():
with allure.step(“1.启动浏览器”):
pass
with allure.step(“2. 登录账号”):
pass
with allure.step(“3. 进入商品管理界面”):
pass
with allure.step(“4. 添加商品数据”):
assert 1 == 2
十六、requests模块详解
16.1.requests发送请求的参数详解
查询字符串参数
下面两种方式,效果一致
session.request(
method=“post”,
url=“http://www.baidu.com”,
params={
“name”: “test”,# 明确的传参
},
)
session.request(
method=“post”,
url=“https://www.baidu.com?name=test”, # 潜在的传参
)
16.2. 表单参数
判断依据:
A 请求头:x-www-form-urlencoded、multipart/form-data
B HTML
昵称:
年龄:
发送请求
session.request(
method=“post”,
url=“https://www.baidu.com”,
data={ # 表单参数
“name”: “test”,
},
)
16.3. JSON参数
判断依据:
A 请求头:application/json
B 请求内容:
{
“msg”: “success”,
“code”: 0,
“data”: {
“navigation”: [],
“banner_list”: [
发送请求
session.request(
method=“post”,
url=“https://www.baidu.com”,
json={ # 表单参数
“name”: “test”,
},
)
16.4.文件参数
判断依据:
a. 请求头:multipart/form-data
b. HTML
<input
type=“file” # 文件上传
name=“file” # 参数名是file
multiple=“”
accept=“image/gif,image/jpeg,image/jpg,image/png”
data-validation-message=“请选择需要上传的图片” required=“”>
发送请求
session.request(
method=“post”,
url=“https://www.baidu.com”,
files={ # 表单参数
“file”: open(“login.html”, “rb”), # 二进制方式打开文件,不要读取
},
)
16.5.requests响应对象的详解
请求发送成功之后,得到接口的响应,它是一个Response的实例对象:
●常用属性
●常用方法
print(‘状态码’, resp.status_code)
print(‘状态说明’, resp.reason)
print(‘响应头’, resp.headers)
print(‘响应Cookies’, resp.cookies)
print(‘响应体(字节)’, resp.content)
print(‘响应体(字符串)’, resp.text)
print(‘响应体(JSON)’, resp.json()) # 可能回报错,所以是个方法
print(‘响应时间’, resp.elapsed)
print(‘字符编码’, resp.encoding)
16.6. 代码示例 requests.session请求 [最终接口框架测试的接口实例]
import requests
import json
session = requests.session()
method = “post”
req = session.request(
method,
url=“http://192.168.31.17:8080/admin/login”,
json={“username”: “admin”, “password”: “macro123”},
headers={“Content-Type”: “application/json”},
)
print(f"content:“,req.content)
print(f"status:”,req.status_code)
print(f"text:",req.text)
print(json.loads(req.text)[“data”][“token”])
#/admin/info?name=admin
user_admin = session.request(
method=“get”,
url=“http://192.168.31.17:8080/admin/info”,
params={“name”: “admin”},
headers={“Content-Type”: “application/json”,“Authorization”: "Bearer " + json.loads(req.text)[“data”][“token”]},
)
print(f"nameadmin:",user_admin.text)
#/product/list?pageNum=1&pageSize=5
product_list = session.request(
method=“get”,
url=“http://192.168.31.17:8080/product/list”,
params={“pageNum”: 1, “pageSize”: 5},
headers={“Content-Type”: “application/json”,“Authorization”: "Bearer " + json.loads(req.text)[“data”][“token”]},
)
print(f"product:",product_list.text)
#/order/list?pageNum=1&pageSize=5
order_list = session.request(
method=“get”,
url=“http://192.168.31.17:8080/order/list”,
params={“pageNum”: “1”, “pageSize”: 5},
headers={“Content-Type”: “application/json”,“Authorization”: "Bearer " + json.loads(req.text)[“data”][“token”]},
)
print(f"order:",order_list.text)
print(f"order_id:",json.loads(order_list.text)[“data”][“list”][0][“id”])
order_id = json.loads(order_list.text)[“data”][“list”][0][“id”]
order_update_receiverInfo = session.request(
method=“post”,
url=“http://192.168.31.17:8080/order/update/receiverInfo”,
json={
“orderId”: order_id,
“receiverCity”: “南京”,
“receiverDetailAddress”: “测试地址”,
“receiverName”: “test”,
“receiverPhone”: “15261448393”,
“receiverPostCode”: “1”,
“receiverProvince”: “1”,
“receiverRegion”: “1”,
“status”: 0
},
headers={“Content-Type”: “application/json”,“Authorization”: “Bearer " + json.loads(req.text)[“data”][“token”]},
)
print(f"order_update_receiverInfo:”,order_update_receiverInfo.text)
更多推荐


所有评论(0)