接口自动化测试之(pytest)
Pytest 是 Python 的一种单元测试框架,与 Python 自带的 unittest 测试框架类似,但是比 unittest 框架使用起来更简洁,效率更高。
一、引入pytest用例管理框架
python: pytest unittest
java: testng junit
pytest 可以和 selenium,requests,appium 实现 web,接口,app 自动化
pytest 作用:
1.发现并找到测试用例 模块名必须以 test_ 开头或者 _test 结尾 类名必须以 Test 开头,不能有 init 方法 用例方法必须以 test 开头
2.按顺序执行测试用例
3.通过断言判断测试结果
4.生成测试报告
pytest 强大的插件
pytest # 本身
pytest-html # 简单的html报告
pytest-xdist # 多线程执行
pytest-ordering # 控制用例的执行顺序
pytest-rerunfailures # 失败用例重跑
pytest-base-url # 设置基础路径(并发,测试,生产,预发布)
allure-pytest # 生成allure报告
插件安装:
1.在项目的根目录创建文件:requirement.txt
2.在文件中写入需要安装的插件名(上面的插件)
3.在项目根目录打开终端控制台,执行命令:pip install -r requirements.txt
4.执行命令后,等待安装插件,安装之后执行:pip list 查看是否安装成功
运行测试用例:
1.命令行(一般调试用):在pycharm的终端控制台,进入到项目的目录,直接运行:
pytest
pytest -vs
2.主函数:在根目录创建 run.py 文件,通过主函数调用 pytest.main()
import pytest
if __name__ == '__main__':
pytest.main()
# pytest.main(["-vs"]) # 输出详细信息
3.结果pytest.ini全局配置文件执行(主流方式):
1.在根目录创建文件:pytest.ini 不管是命令行还是主函数,都会读取 pytest.ini 配置文件来执行 pytest.ini
2.文件一般放在项目的根目录 pytest.ini 文件的文件名和参数名都是固定的
3.需要注意编码格式(如果默认的UTF-8报错就改成GBK,GBK报错就改成UTF-8) 文件内容开头必须带:[pytest] 标志
pytest.ini
[pytest]
# 命令行参数
# 常见的:
; --html=./reports/report.html 简单的测试用例报告
; --reruns 2 # 失败用例重跑
; -m "smoke or user" # 冒烟测试
; -p no:warnings # 完全禁用警告捕获
# 配置参数: v 输出详细信息 s 表示调试信息
addopts = -vs -p no:warnings
# 配置执行的用例位置(配置执行哪个用例文件)
testpaths = ./testcases
# 配置修改默认的模块规则(配置执行哪个模块)
python_files = test_*.py
# 配置修改默认的类规则(配置执行哪个类)
python_classes = Test*
# 配置修改默认的用例规则(配置执行哪个用例)
python_functions = test_*
# 配置基础路径
base_url = https://192.168.1.93:8887
# 标记
markers =
smoke:冒烟测试用例(只运行被标记的用例)
# markes 标记 :在上面的参数 addopts 参数中加入 -m "smoke" 就会只运行被标记的用例
# 例如 addopts = -vs -m "smoke" 只会运行被标记了 smoke 的用例
# 例如 addopts = -vs -m "smoke or user" 同时运行被标记了 smoke 和 user 的用例
# 在用例前面加上:@pytest.mark.smoke
# 在用例前面加上:@pytest.mark.user
# markes = smoke:冒烟测试
# user:用户名
二、Pytest 的前后置(不常用,了解就行),固件、夹具(常用)
Jmeter 和 Postman:前置脚本和后置脚本
pytest 默认前置和后置函数
现在这种方法很少用到了(了解就行)
def setup_method(self):
print("每个用例前的操作")
def teardown_method(self):
print("每个用例后的操作")
def setup_class(self):
print("每个类前的操作")
def teardow_class(self):
print("每个类后的操作")
class TestSendRequest:
# 全局变量,类变量
admin_token = ''
def setup_method(self):
print("用例请求之前:执行开始语句")
def teardown_method(self):
print("用例请求之后:执行结束语句")
# 1.system admin 登录,获取 token
@pytest.mark.smoke
def test_get_systemadmin_token(self):
url = 'https://192.168.1.93:8887/api/tokens'
form_data = {
"username": "admin",
"password": "admin"
}
response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)
class TestSendRequest:
# 全局变量,类变量
admin_token = ''
def setup_class(self):
print("类请求之前:执行开始语句")
def teardown_class(self):
print("类请求之后:执行结束语句")
# 1.system admin 登录,获取 token
@pytest.mark.smoke
def test_get_systemadmin_token(self):
url = 'https://192.168.1.93:8887/api/tokens'
form_data = {
"username": "admin",
"password": "admin"
}
response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)
三、使用 fixture 固件结合contest.py文件实现前后置
fixture 是装饰器,语法规则:
@pytest.fixture(scope="作用域",autouse="自动执行",params="参数化",ids="参数别名",name="装饰器别名")
scope="function" 作用于函数
scope="class" 作用于类(自动执行)
scope="module" 作用于模块(自动执行)
scope="session" 作用于会话(自动执行)
autouse=True 表示所有的用例自动执行(都会执行)
autouse=False 表示所有的用例手动执行(需要传参才会执行)
代码示例:
@pytest.fixture(scope="function", autouse=True, name="ps")
def exe_sql():
print("请求之前:执行开始语句")
yield # 固定写法,yield 后面的均会在作用域后执行
print("请求之后:执行结束语句")
class TestSendRequest:
# 全局变量,类变量
admin_token = ''
# 1.system admin 登录,获取 token
@pytest.mark.smoke
def test_get_systemadmin_token(self):
url = 'https://192.168.1.93:8887/api/tokens'
form_data = {
"username": "admin",
"password": "admin"
}
response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)
# 获取 token 的值(正则表达式提取)
result = response.text
TestSendRequest.admin_token = re.search('"access_token":"(.*?)"', result).group(1) # 正则表达式提取token值,通过下标取值
# 2.list network configurations
# @pytest.mark.user
def test_get_network_information(self, ps): # autouse=False(如果为True,ps 不用传)所以,在使用时,传入的参数名需要改为固件的别名(name="ps"),如果用固件本名(exe_sql),就会报错
url = 'https://192.168.1.93:8887/api/network'
headers = {"Authorization": "Bearer " + TestSendRequest.admin_token}
RequestUtil().all_send_request(method='get', url=url, headers=headers, verify=False)sh
输出结果:
testcases/test_request_systemadmin.py::TestSendRequest::test_get_systemadmin_token 请求之前:执行开始语句
{"access_token":"9c97c7ca-5f98-4c6e-afe8-4f61bb591ba9","role":"SystemAdmin","expires_at":"2024-04-02T09:48:00.657201909Z","expires_in":1800}
PASSED请求之后:执行结束语句
testcases/test_request_systemadmin.py::TestSendRequest::test_get_network_information 请求之前:执行开始语句
{"web_domain":"192.168.1.93","private_ipv4":"192.168.1.93","public_ipv4":"192.168.1.93","enable_ipv6":true,"private_ipv6":"","public_ipv6":"","primary_dns_server":"","secondary_dns_server":""}
PASSED请求之后:执行结束语句
在一个文件中设置的固件 fixture 只能在当前文件中起作用,如果希望对所有py文件起作用,那么要用到 conftest.py 文件,
把 fixture 装饰器单独放在 conftest.py 文件中,就可以对所有文件起作用(通过 conftest.py 文件控制 Fixture 的作用域)
1.conftest.py 名字是固定的,不能变。专门用来存放 fixture 固件的。(testcases 根目录创建 conftest.py)
2.conftest.py 的固件在使用时都不需要导包。默认同级或子级的py文件都可以使用
3.conftest.py 文件可以有多个。作用域都是:同级或子级
4.conftest.py 的固件执行顺序,执行方式为自动时: 外层的优先级高于内层。手动时: 传参时,位置在前的优先级更高
conftest.py
scope=“作用域”,autouse=“自动执行”,params=“参数化”,ids=“参数别名”,name=“装饰器别名”(后面三个参数意义不大,了解就行)
conftest.py
# fixture 固件
import pytest
# scope, autouse 用法
# @pytest.fixture(scope="function", autouse=True)
# def exe_sql(): # 括号内加 request 固定写法
# print("请求之前:执行开始语句")
# yield
# print("请求之后:执行结束语句")
# params, ids, name用法
@pytest.fixture(scope="function", autouse=False, params=[["鲁迅", "周树人"], ["实华", "朱自清"]], ids=["TEST1", "TEST2"], name="Test123")
def exe_sql(request): # 括号内加 request 固定写法
print("请求之前:执行开始语句")
yield request.param # 通过 request 将参数传出 固定写法
print("请求之后:执行结束语句")
test_system_admin_login.py
def test_get_systemadmin_token(self, exe_sql): # 传参 conftest.py:exe_sql
print(exe_sql)
url = 'https://192.168.1.93:8887/api/tokens'
form_data = {
"username": "admin",
"password": "admin"
}
response = RequestUtil().all_send_request(method='post', url=url, json=form_data, verify=False)
输出结果:
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login[TEST1] 请求之前:执行开始语句
['鲁迅', '周树人']
{"access_token":"64b1c57e-e931-44a2-8135-ba08030c4fbd","role":"SystemAdmin","expires_at":"2024-04-07T09:34:52.673783772Z","expires_in":1800}
PASSED请求之后:执行结束语句
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login[TEST2] 请求之前:执行开始语句
['实华', '朱自清']
{"access_token":"4341a4fe-cfc4-419e-8fc3-6cb049899bad","role":"SystemAdmin","expires_at":"2024-04-07T09:34:52.681468374Z","expires_in":1800}
PASSED请求之后:执行结束语句
分层前后置
testcases 根目录下的 conftest.py
import pytest
@pytest.fixture(scope="function", autouse=True)
def login():
print("登录")
testcases/system_admin/conftest.py 层级目录下的 conftest.py
import pytest
@pytest.fixture(scope="function", autouse=True)
def goto_systemadmin():
print("在system admin中执行")
输出结果:
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_systemadmin_login 登录
在system admin中执行
{"access_token":"1d56b5b6-9698-4c4f-85e9-457859a966b1","role":"SystemAdmin","expires_at":"2024-04-07T09:59:58.893690596Z","expires_in":1800}
PASSED
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_retrieve_network_configurations 登录
在system admin中执行
{"web_domain":"192.168.1.93","private_ipv4":"192.168.1.93","public_ipv4":"192.168.1.93","enable_ipv6":true,"private_ipv6":"23c8:f5c7:4846:8210:50b0:39c0:66e0:8944","public_ipv6":"23c8:f5c7:4846:8210:50b0:39c0:66e0:8944","primary_dns_server":"162.21.10.98","secondary_dns_server":"162.21.10.98"}
PASSED
testcases/system_admin/test_system_admin_login.py::TestSystemAdmin::test_update_network_configurations 登录
在system admin中执行
PASSED
testcases/tenant/test_tenant_admin_login.py::TestTenantAdmin::test_tenant_login 登录
{"access_token":"33c12f2f-df79-4ffc-9de2-9a7557f8d162","role":"Admin","expires_at":"2024-04-07T09:59:58.911056241Z","expires_in":1800}
PASSED
testcases/tenant/test_tenant_admin_login.py::TestTenantAdmin::test_list_user 登录
{"count":0,"items":[{"id":"825642789151703040","enabled":true,"name":"101","email":"Freeda.Schiller19@gmail.com","display_name":"Wendy Jacobi","role":"Admin","extension_number":"101","mobile_phone":"366-786-5189","work_phone":"693-287-8086","home_phone":"520-509-7189","twitter":"438-525-9936","facebook":"428-526-2094","linkedin":"763-492-8441","instagram":"251-845-9758","avatar":"","avatar_file_id":"","avatar_file_name":"","avatar_file_size":0,"avatar_file_url":"","created_at":"2024-03-28T08:09:40.635813Z"}]}
PASSED
四、接口自动化测试框架(通过文件保持中间变量实现接口关联)
原因:
1.类变量不能跨py文件使用
2.统一管理中间变量
项目的根目录新建 yaml 文件(extract.yaml)
项目的根目录 > commons 目录下新建 yaml_util.py 文件(封装)
yaml_util.py
import os
import yaml
# 写入
def write_yaml(data):
with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="a+") as f:
yaml.dump(data, stream=f, allow_unicode=True)
# 读取
def read_yaml(key):
with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="r") as f:
value = yaml.load(f, yaml.FullLoader)
return value[key] # 返回读取到的值
# 清空
def clear_yaml():
with open(os.getcwd() + "/extract.yaml", encoding="utf-8", mode="w") as f:
f.truncate()
test_system_admin_login.py
# System Admin 登录
@pytest.mark.smoke
def test_systemadmin_login(self):
url = 'https://192.168.1.93:8887/api/tokens'
body = {
"username": "admin",
"password": "admin"
}
response = RequestUtil().send_request(method='post', url=url, json=body, verify=False)
# 获取 token(json.path 提取并写入到extract.py文件中)
result = response.json()
value = jsonpath.jsonpath(result, '$.access_token')
datas = {"admin_token": "Bearer " + value[0]}
write_yaml(datas) # 写入到extract.py文件中
# 检索网络配置
@pytest.mark.smoke
def test_retrieve_network_configurations(self):
url = 'https://192.168.1.93:8887/api/network'
headers = {
"Authorization": read_yaml('admin_token') # 读取extract.py文件中admin_token的值
}
RequestUtil().send_request(method='get', url=url, headers=headers, verify=False)
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)