Python3.8以上
Allure
requirements.txt里面的依赖模块
Allure官网地址:https://docs.qameta.io/allure/
导航到 2.1. Installing a commandline
根据截图中的步骤进行操作就能安装成功Allure
Base_V0.1
版本说明
Base:非正式版
Release:正式版
Vxxx:版本号
SimpleAPI,顾名思义,就是希望让接口测试变得简单
SimpleAPI就是一个自动化测试框架,写这个框架的初衷,是为了给一些想学习自动化,但又无从下手的同学提供一些思路。这个框架我会坚持每周更新,每次更新发生的变化,我都会用“分支”记录下来,供后续每一位想要学习的新同学参考。
SimpleAPI的初始版本是“Base_V0.1”,如果你想学习这个框架,那么,我希望你能从“Base_V0.1”这个版本着手起步。后续的迭代,所有功能都是基于“Base_V0.1”的代码进行优化
特别注意⚠️:Base_V0.1版本的功能还非常简单,目前还不够智能、不够自动化。所以,Base_V0.1版本只是体验、学习为主,不建议直接在生产中使用(通俗点说:不够完善,别造那么多数据先)。到了Base_V0.2版本,我将优化数据的存储方式。Base_V0.2之后,数据格式不再变动,到时候再大量造数据。
. # 项目根目录:SimpleAPI
├── Data # 用例数据存放目录
│ └── haloLogin # 用例模块目录
│ └── login.yaml # 某个接口的用例数据
├── README.md # 框架说明文档
├── README # README.md需要的一些图片、文字数据、等
├── Report # 生成Allure报告数据的存放目录
├── RunInit # 运行代码前的配置、检测
│ └── createReport.py # 创建报告目录
├── SystemFile # 系统依赖文件的存放目录
│ ├── pathEnum.py # 项目各个目录的绝对路径存放文件
│ ├── readUserConf.py # 读取YAML文件方法文件
│ └── user.conf.yaml # 用户配置文档
├── TestCase # 测试用例存放目录
│ └── test_loginModule.py # 接口用例
├── Utils # 工具类存放目录
│ ├── dataUtils # 数据相关的工具类目录
│ │ ├── setRequestData.py # 设置请求数据工具类
│ │ └── yamlUtility.py # YAML文件操作工具类
│ ├── fileUtils # 文件、目录操作工具类目录
│ │ └── directoryUtils.py # 目录操作工具类
│ └── requestUtils # 请求相关的工具类目录
│ └── mergeUrl.py # 将host和request path合并的方法
├── main.py # 程序主入口
├── pytest.ini # pytest配置文件
└── requirements.txt # 框架依赖的第三方模块
用户配置文件存放位置:SimpleAPI -> SystemFile -> user.conf.yaml
配置文件详细说明
# host配置:在接口测试中,host基本是不改动的,所以可以在这里配置我们的host。发请求前,来这里取对应host数据即可
# evn:环境配置(字段名不能改)
evn:
# host:具体使用哪个环境(字段名不能改,字段值随便改。注意:host的字段值一定要和下面的字段名对应上)
host: test
# 开发环境(名字、值,都能随便改,上面的host值,和下面4个名,其中一个对上就行了)
dev: http://175.178.37.196
# 测试环境
test: http://175.178.37.196
# 预发布环境
staging: http://175.178.37.196
# 生产环境
product: http://175.178.37.196
# 我们以百度为例子,创建一个百度请求的数据。注意,记得把host改成:https://www.baidu.com
case_1:
title: 发送个百度请求
method: get
case_2:
title: 不存在的路径,请求失败
method: get
path: /asdasdad
请求数据解释
case_1:用例的标志,必填
title:用例名称,可以忽略不填
method:请求的方法,必填
path:请求的路径,可以不填
请求示例
请求网址: http://175.178.37.196/api/admin/login
请求方法: POST
请求头: Content-Type: application/json
请求体:
authcode: null
password: "12345678"
username: "admin"
请求成功响应示例
{
"status": 200,
"message": "OK",
"devMessage": null,
"data": {
"access_token":"67c4442bc8434ea8828b52df03361cac",
"expired_in":86400,
"refresh_token":"77e3a390b2444895ba11d837c6b64054"
}
}
根据以上的示例,我们来设计对应的数据
case_1:
title: 冒烟用例
path: /api/admin/login
method: POST
header:
Content-Type: application/json
params:
authcode: null
password: "12345678"
username: "admin"
assert:
- "'OK' == r.json()['message']"
- 200 == r.status_code
case_2:
title: 账号不存在,登录失败
path: /api/admin/login
method: POST
header:
Content-Type: application/json
params:
authcode: null
password: "1234567"
username: "admin"
assert: "'用户名或者密码不正确' == r.json()['message']"
case_3:
title: 密码错误,登录失败
path: /api/admin/login
method: POST
header:
Content-Type: application/json
params:
authcode: null
password: "12345678"
username: "admins"
assert: "'用户名或者密码不正确' == r.json()['message']"
请求数据解释
case_1:用例的标志,必填
title:用例名称,可以忽略不填
method:请求的方法,必填
path:请求的路径,可以不填
header:请求头,可以不填,根据实际情况来就行了
params:请求参数,可以不填,根据实际情况来就行了
assert:断言语句,里面存放Python语法,具体写什么内容,要根据你的用例来
from Utils.dataUtils.setRequestData import SetRequestData
from SystemFile.pathEnum import get_path
from requests import request
import logging
import pytest
import allure
import time
import os
# 标记模块名称,最后会在报告中显示
@allure.feature("登录模块")
class TestLogin:
# 用例文件的路径
file = os.path.join(os.path.join(get_path("data"), "haloLogin"), "login.yaml")
# 读取文件中的数据
case = SetRequestData(file).set_case()
@allure.story("登录接口测试") # 用例名称,可以直接写接口名称
@allure.title("{title}") # 用例标题
# parametrize是参数化的方法
@pytest.mark.parametrize("title, url, method, header, paras, assert_str", case)
# 接口用例
def test_case_1(self, title, url, method, header, paras, assert_str):
# 使用request发送接口请求
r = request(method=method, url=url, headers=header, json=paras)
# 将请求对象转换成"utf-8"编码
r.encoding = "utf-8"
# 控制台输出日志
log = logging.getLogger('响应信息')
log.setLevel(logging.DEBUG)
# 对响应数据做断言
if type(assert_str) == type(str()):
assert eval(assert_str)
log.info(r.text)
elif type(assert_str) == type([]):
for assert_ in assert_str:
assert eval(assert_)
log.info(r.text)
time.sleep(5) # 我这里使用的是测试项目的登录接口,由于不能频繁访问,所以加个等待时间
对于某些Python基础较差的同学来说,可能看完后仍然是一头雾水。不够没关系,学习是需要过程的,没有一天建成的罗马,自然也没有一天就能完全掌握的技术。既然如此,那我们就直接开始实践吧,兄弟们,跟我冲~
我们以CSDN文章搜索接口做个例子
请求示例
请求网址: https://silkroad.csdn.net/api/v2/assemble/list/channel/pc_hot_word?
请求参数:
user_foormark: 1
channel_name: pc_hot_word
size: 20
platform: pc
imei: 10_20051326840-1646992611076-531956
请求方法: GET
响应示例
{
"code": 200,
"data": {
"ext": {},
"size": 10,
"items": [
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python log 青色",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-1-python log 青色-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "1",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-1-python log 青色-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-1-python log 青色-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 1,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python3 高亮打印",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-2-python3 高亮打印-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "2",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-2-python3 高亮打印-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-2-python3 高亮打印-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 2,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python print 颜色",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-3-python print 颜色-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "3",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-3-python print 颜色-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-3-python print 颜色-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 3,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "pycharm没有pytest运行",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-4-pycharm没有pytest运行-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "4",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-4-pycharm没有pytest运行-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-4-pycharm没有pytest运行-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 4,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python assert 失败",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-5-python assert 失败-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "5",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-5-python assert 失败-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-5-python assert 失败-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 5,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "invalid placeholder in string",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-6-invalid placeholder in string-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "6",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-6-invalid placeholder in string-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-6-invalid placeholder in string-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 6,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python删除目录",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-7-python删除目录-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "7",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-7-python删除目录-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-7-python删除目录-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 7,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "pycharm 创建生成注释",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-8-pycharm 创建生成注释-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "8",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-8-pycharm 创建生成注释-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-8-pycharm 创建生成注释-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 8,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python递归函数中的静态变量",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-9-python递归函数中的静态变量-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "9",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-9-python递归函数中的静态变量-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-9-python递归函数中的静态变量-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 9,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
},
{
"ext": null,
"resourceId": "",
"mediaAssetInfo": null,
"productId": "python获得一个目录下所有结构",
"reportData": {
"eventClick": true,
"data": {
"mod": "",
"extra": "{\"utm_medium\":\"distribute.pc_hot_word.none-task-hot_word-landing-10-python获得一个目录下所有结构-null-null.186^v4^control\",\"dist_request_id\":\"1659976388516_95735\"}",
"dist_request_id": "1659976388516_95735",
"ab_strategy": "default",
"index": "10",
"strategy": "landing"
},
"urlParams": {
"utm_medium": "distribute.pc_hot_word.none-task-hot_word-landing-10-python获得一个目录下所有结构-null-null.186^v4^control",
"depth_1-utm_source": "distribute.pc_hot_word.none-task-hot_word-landing-10-python获得一个目录下所有结构-null-null.186^v4^control"
},
"eventView": true
},
"recommendType": "ali",
"index": 10,
"style": "word_1",
"strategyId": "landing",
"productType": "hot_word"
}
]
},
"message": "success"
}
将SimpleAPI -> SystemFile -> user.conf.yaml的 evn -> test 改为:https://silkroad.csdn.net,其他不用动。修改好之后,应该和底下一样(user.conf.yaml的注释太碍眼的话,可以删掉)
evn:
host: test
dev: http://175.178.37.196
test: https://silkroad.csdn.net
staging: http://175.178.37.196
product: http://175.178.37.196
接下来就要构建我们的接口数据和断言数据了
清空 SimpleAPI -> Data -> haloLogin -> login.yaml 里面的所有内容
把下面的内容,复制到login.yaml 里面
case_1:
title: 发送个成功的请求
method: get
path: /api/v2/assemble/list/channel/pc_hot_word?
params:
user_foormark: 1
channel_name: pc_hot_word
size: 20
platform: pc
imei: 10_20051326840-1646992611076-531956
assert:
- 200 == r.json()["code"]
- 200 == r.status_code
case_2:
title: size等于0,则没有数据返回
method: get
path: /api/v2/assemble/list/channel/pc_hot_word?
params:
user_foormark: 1
channel_name: pc_hot_word
size: 0
platform: pc
imei: 10_20051326840-1646992611076-531956
assert:
- 0 == len(r.json()["data"]["items"])
- 200 == r.status_code
开始编写我们的用例啦
将 SimpleAPI -> TestCase -> test_loginModule.py 里面的内容清空,替换成下面的内容
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @time: 2022-08-08 09:50:44
# @author: 鹰眼测试
from Utils.dataUtils.setRequestData import SetRequestData
from SystemFile.pathEnum import get_path
from requests import request
import logging
import pytest
import allure
import os
# 标记模块名称,最后会在报告中显示
@allure.feature("CSDN搜索模块")
class TestLogin:
file = os.path.join(os.path.join(get_path("data"), "haloLogin"), "login.yaml")
case = SetRequestData(file).set_case()
@allure.story("搜索接口测试") # 用例名称,可以直接写接口名称
@allure.title("{title}") # 用例标题
@pytest.mark.parametrize("title, url, method, header, params, assert_str", case)
def test_case_1(self, title, url, method, header, params, assert_str):
r = request(method=method, url=url, headers=header, params=params)
r.encoding = "utf-8"
log = logging.getLogger('响应信息')
log.setLevel(logging.DEBUG)
if type(assert_str) == type(str()):
assert eval(assert_str)
log.info(r.text)
elif type(assert_str) == type([]):
for assert_ in assert_str:
assert eval(assert_)
log.info(r.text)
运行 SimpleAPI -> main.py 文件即可
最后的结果如截图(看不懂英文的,在左下角有个En,点击一下切换中文就行了)
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。