1、接口项目现状
1.1 问题
问题1:问题1:没有接口设计文档,只有Swagger
Swagger 是一个开源的 API 设计和文档工具,它可以帮助开发人员更快、更简单地设计、构建、文档化和测试 RESTful API。Swagger 可以自动生成交互式 API 文档、客户端 SDK、服务器 stub 代码等,从而使开发人员更加容易地开发、测试和部署 API。
问题2::Swagger很粗不细致
Swagger自动生成。有请求和返回值。
问题3:接口很多
登录接口+商铺管理接口。其中登录接口没有swagger。
1.2 接口测试范围分析
1.2.1 具体任务分发人:
如果式是测试部经理,则从项目经理手中接受任务,分发给测试工程师
如果是测试工程师,则从测试经理手中接受测试任务。
1.2.2 确定接口测试范围
1、测试找到对应的编写依据
完成成果:接口测试范围列表
依据1:Swagger说明
依据2:开发工具以及前后台分析
2、与相关的测试负责人明确测试的内容
只是前端,不要自己去猜,要去问
只是后端,一般都是测后端
前端+后端,不排除这种情况,这种情况需要将返回值与界面的数据进行对比。
接口的共性本质:包括接口地址、接口类型、接口参数、接口返回值。
接口类型分为协议类型和请求类型。
请求:get、post、put、delete等
协议类型:http、https、webserver、socket、
接口跟实现方式没有关系,跟app,web,小程序等没有关系。
1.2.3 通过浏览器端的开发工具
与登录相关的接口的测试范围进行扩展明确,
登录从第一个验证码请求到最后跳转成功,总共有5个请求
分析后端请求发送,避免测试的遗漏
登录时按F12开发者模式,选择html,右键Copy--Copy outerHTML
找个记事本粘贴进去,另存为html
另存为test.html格式
双击打开可以看到没有渲染过的html页面
查看network-Fetch/XHR
登录之后,又出了几个请求
2、获取验证码图片接口
2.1 接口进行分析
浏览器端的开发工具
接口请求+方法+返回值
这是一个get请求
2.2 接口测试用例设计
2.2.1 接口测试用例和功能测试用例的异同:
设计思路方法完全相同
用例要素不同:接口地址、接口类型、没有操作步骤
这里要明确一点,接口测试需求分析中未知空白
我们可以从已知过渡到未知的东西。
首先我们功能测试需求分析确定的式功能测试范围,而接口测试范围确定的式接口测试范围。
功能测试分析各种类型测试点,而接口测试分析各种类型接口的测试点。
2.2.2 接口测试用例设计重点难点
返回值验证点设计,一个或多个,
1、msg=操作成功
2、uuid每次要不相同
2.3 接口冒烟测试
1、先新建一个Collection,新建一个集合来分类。
点击New--Collection
输入一个名字01智慧物业-登录
2、新加一个请求
这个获取登录验证图片的接口是一个get请求,输入接口地址,正确,无空格
选择类型get
未携带参数,点击send,请求返回成功。
3、设置检查点
1、提取json进行msg比对
点击Tests,再选择右边的Response body:JSON value check
pm.test("测试msg返回值", function () {
var jsonData = pm.response.json();
pm.expect(jsonData.msg).to.eql("操作成功");
});
此时断言失败,就会再Test Results里面展示出来'FAIL'
如果通过就会返回“PASS”
2、提取uuid,放入log中
1、定义postman的response的json变量: var jd=pm.response.json();
2、把提取的uuid内容放入变量中 : var uuid=jd.uuid
3、把提取 的uuid放入log中 : console.log("uuid",uuid)
4、查看日志 :console.log(uuid)
var jd=pm.response.json();
var uuid=jd.uuid
console.log("uuid",uuid)
pm.globals.set("uid", uuid);
点击左下角Console
2.4 接口自动化脚本研发
取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
print(type(response))
uuid = response['uuid']
print(uuid)
3、登录接口测试
3.1 接口测试分析
通过开发者工具进行接口的分析,地址、类型、参数、返回值。
3.2 接口测试用例
登录测试用例:正常登录接口测试
异常登录接口测试:等价类+边界值
分别构造输入参数的异常:错误、空
3.3 接口冒烟测试
使用postman进行登录接口冒烟测试
1、首先要分辨一下接口的类型:
可以看到是application/json格式
设置请求的格式
再设置header
脚本中要进行header参数的说明
response=requests.post(url,data=userjson,headers={"Content-Type":"application/json"}).json()
2、如何把验证码图片接口中返回的uuid传入登录接口
我们看到这个接口中有四个参数,其中uuid是从上个接口分配下来的,那么如何将它提取出来作为这个接口的参数呢?
在验证码图片接口结果Tests中增加全局变量
在登录接口参数中,替换uuid的内容
3.4 接口自动化脚本研发
3.4.1 登录接口测试V1.0
登录接口的语法跑通
所有参数都是常量
1、接口请求发送参数设置
定义了字典类型的登录接口参数,提示类型不相符
把dict格式转化为json格式
import json
userjson=json.dumps(userinfo)
在发送请求时,需要加入headers参数json格式说明
response=requests.post(url,data=userjson,headers={"Content-Type":"application/json"}).json()
2、提取检查点
直接以json方式获取请求结果
res=requests.post(...).json()
res['key']来获取检查点的值并进行判断
if response["msg"]=='操作成功' and response["username"]=='admin':
总体v1.0
#********************************************************************************
#登录接口V1.0脚本实现
#功能:常量参数传递
# 问题1:不支持字典格式,只支持json格式,进行类型转换,同时加入header参数
#********************************************************************************
#导入类库
import requests
import json
#定义接口地址
url="https://svr-6-9009.share.51env.net/prod-api/login"
#定义接口参数
userinfo={
"username":"admin",
"password":"admin123",
"code":"51testing",
"uuid":"eaaef4d6bb7e4438894669dc07fd050c"
}
print(type(userinfo))
# 将字典类型转化为json类型
userjson=json.dumps(userinfo)
print(type(userjson))
#发送接口请求
response=requests.post(url,data=userjson,headers={"Content-Type":"application/json"}).json()
print(response)
print(response["msg"])
#对比实际和预期结果
if response["msg"]=='操作成功' and response["username"]=='admin':
print("登录接口成功")
else:
print("登录接口失败")
3.4.2 登录接口测试V2.0
uuid需要从验证码图片接口的返回值中进行获取
追加一个功能,提取uuid
response = requests.get(CaptchaURL).json()
uuid = response['uuid']
通过变量定义进行uuid值的传递
userinfo={
"username":"admin",
"password":"admin123",
"code":"51testing",
"uuid":uuid
}
V2.0总体代码:
#********************************************************************************
#登录接口V2.0脚本实现
#功能:1、调用图片验证码接口获取uuid
# 2、将获取到的uuid传入登录接口中
#********************************************************************************
# 导入类库
import requests
import json
#*********************************************************************
取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
print(type(response))
uuid = response['uuid']
print(uuid)
# 获
#*********************************************************************
#定义接口地址
url="https://svr-6-9009.share.51env.net/prod-api/login"
#定义接口参数
userinfo={
"username":"admin",
"password":"admin123",
"code":"51testing",
"uuid":uuid
}
print(type(userinfo))
# 将字典类型转化为json类型
userjson=json.dumps(userinfo)
print(type(userjson))
#发送接口请求
response=requests.post(url,data=userjson,headers={"Content-Type":"application/json"}).json()
print(response)
print(response["msg"])
#对比实际和预期结果
if response["msg"]=='操作成功' and response["username"]=='admin':
print("登录接口成功")
else:
print("登录接口失败")
3.4.3 登录接口测试V3.0
面向过程的封装
定义两个方法:
#定义获取验证码uuid的方法
def getuuid():
#执行登录接口的测试
def login_test(uuid):
参数传递:
1、全局公共变量
外部定义uuid
引用时要进行说明
global uuid
2、返回值+参数
方法1:return uuid
def getuuid():
# 取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
print(type(response))
# 全局uuid引用说明
global uuid
uuid = response['uuid']
print(uuid)
return uuid
方法2:def login_test(uuid)
调用时要注意
总体程序V3.0
#********************************************************************************
#登录接口V3.0脚本实现
#功能:1、调用图片验证码接口获取uuid
# 2、将获取到的uuid传入登录接口中
# 3、分别对两个功能进行过程封装
#********************************************************************************
#导入类库
import requests
import json
#定义全局的uuid变量
uuid='aaa'
#定义获取验证码uuid的方法
def getuuid():
# 取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
print(type(response))
# 全局uuid引用说明
global uuid
uuid = response['uuid']
print(uuid)
return uuid
#执行登录接口的测试
def login_test(uuid):
# 定义接口地址
url = "https://svr-6-9009.share.51env.net/prod-api/login"
# 定义接口参数
print("登录方法中",uuid)
userinfo = {
"username": "admin",
"password": "admin123",
"code": "51testing",
"uuid": uuid
}
print(type(userinfo))
# 将字典类型转化为json类型
userjson = json.dumps(userinfo)
print(type(userjson))
# 发送接口请求
response = requests.post(url, data=userjson, headers={"Content-Type": "application/json"}).json()
print(response)
print(response["msg"])
# 对比实际和预期结果
if response["msg"] == '操作成功' and response["username"] == 'admin':
print("登录接口成功")
else:
print("登录接口失败")
# 使用main函数进行方法调用
if __name__ == '__main__':
uid=getuuid()
login_test(uid)
3.4.4 登录接口测试V4.0
面向对象的封装
定义一个类:
包含两个方法
#方法1:获取验证码对应的uuid
def getuuid(self):
# 执行登录接口的测试
def login_test(self):
包含一个属性:
注意:self.名称
可以不用进行返回和传参,直接使用属性
进行类的实例化调用:
登录程序V4.0
# ********************************************************************************
# 登录接口V4.0脚本实现
# 功能:1、调用图片验证码接口获取uuid
# 2、将获取到的uuid传入登录接口中
# 3、分别对两个功能进行面向对象封装
# ********************************************************************************
# 导入类库
import requests
import json
# 定义一个类:包含两个方法和一个属性
class login_interface():
# 定义一个属性
def __init__(self):
self.uuid=''
#方法1:获取验证码对应的uuid
def getuuid(self):
# 取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
print(type(response))
# 全局uuid引用说明
# global uuid
self.uuid = response['uuid']
print(self.uuid)
# return uuid
# 执行登录接口的测试
def login_test(self):
# 定义接口地址
url = "https://svr-6-9009.share.51env.net/prod-api/login"
# 定义接口参数
print("登录方法中", self.uuid)
userinfo = {
"username": "admin",
"password": "admin123",
"code": "51testing",
"uuid": self.uuid
}
print(type(userinfo))
# 将字典类型转化为json类型
userjson = json.dumps(userinfo)
print(type(userjson))
# 发送接口请求
response = requests.post(url, data=userjson, headers={"Content-Type": "application/json"}).json()
print(response)
print(response["msg"])
# 对比实际和预期结果
if response["msg"] == '操作成功' and response["username"] == 'admin':
print("登录接口成功")
else:
print("登录接口失败")
# 进行实例化调用
if __name__ == '__main__':
#实例化对象
loginObj=login_interface()
#调用其中的方法
loginObj.getuuid()
loginObj.login_test()
4、获取登录用户信息接口
4.1 接口测试分析
这是个get请求,没带参数
接口返回结果:
4.2 接口冒烟测试
1、在postman中加入接口认证:
返回成功
2、如何自动获取token
a).首先判断哪个接口生成token,登录接口中生成
b).从登录接口获取token
声明postman的响应json格式变量
var jd=pm.response.json();
提取响应结果中的token
var tokenvalue=jd.token
传入全局变量
pm.globals.set("token", tokenvalue);
3、把token传入获取用户信息接口
4.3 接口脚本研发
调用获取用户信息接口时,报401,原因是没有token值。
token的使用机理?
uuid&token
uuid是通用唯一标识符的意思。
这个项目是典型的Java 前后端分离方案
后端是JavaEE/Spring-boot /mybatis/MySQl
前端是Vue/Element-ui
4.3.1 获取登录用户信息接口V1.0
1、将token作为常量传入
写入headers属性参数中
header={'Authorization':'Bearer '+token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
提取检查点的信息时要注意json的层次
msg=response['msg']
获取登录用户信息接口V1.0代码:
# **********************************************************
# 获取登录用户信息接口V1.0脚本实现
# 功能:常量参数传递
# 问题1:token的值放在请求的headers中是否可行?
# ***********************************************************
# 导入类库
import requests
# 定义接口地址
url='https://svr-6-9009.share.51env.net/prod-api/getInfo'
# 定义token参数
token="eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjRlMjRhMjg0LWQzZTMtNDgzYy1iYjE2LWJmMWMyZDU3ZDkwYiJ9.P26W_eLXvbEgnD4rXHRVClhUg4JpiK3i_4lskSqGKfKluv8KZCRJ8LdiHRWUD5Zzagr-pCiSrrr80_btyIVe6w"
header={'Authorization':'Bearer '+token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
print(response)
# 进行检查点的验证
# 提取msg
msg=response['msg']
print(msg)
# 提取电话
phone=response['user']['phonenumber']
print(phone)
# 提取邮箱
email=response['user']['email']
print(email)
#提取用户名
username=response['user']['userName']
print(username)
if (username=='admin') and (phone=='13800138000') and (email=="13800138000@139.com") and (msg=='操作成功'):
print('获取用户信息接口测试成功')
else:
print("获取用户信息接口测试失败")
4.3.2 获取登录用户信息接口V2.0
使用assert进行检查点的验证
这里使用try: except: else:对断言进行分析。
结合try方法对assert进行捕获
assert成功时,没有任何提示,程序继续进行
assert失败时,会提示系统错误,程序会停止运行
获取登录用户信息接口V2.0程序:
# **********************************************************
# 获取登录用户信息接口V2.0脚本实现
# 功能:常量参数传递
# 问题1:assert与异常处理try的结合技术
# ***********************************************************
# 导入类库
import requests
# 定义接口地址
url='https://svr-6-9009.share.51env.net/prod-api/getInfo'
# 定义token参数
token="eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjFlYTgxZWEyLWQyYzEtNDUzMi1iNzNhLTA1OTA3ZGExMGE1YiJ9.fwXfiDChh6NqnpHf0nLt6TcqIFeqD2fB1PDPnJxTKAx11kgBquIQNJyMO15XZrFKRSX8KF5uFgYH_hblHg4phQ"
header={'Authorization':'Bearer '+token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
print(response)
# 进行检查点的验证
# 提取msg
msg=response['msg']
print(msg)
# 通过assert断言进行msg的验证
# 提取电话
phone=response['user']['phonenumber']
print(phone)
# 提取邮箱
email=response['user']['email']
print(email)
#提取用户名
username=response['user']['userName']
print(username)
#对assert进行异常捕获
try:
assert msg == '操作成功'
assert phone=='13800138000'
assert email=='13800138000@139.com'
assert username=='admin'
except:
print("获取用户信息接口测试失败")
else:
print("获取用户信息接口测试成功")
4.3.3 获取登录用户信息接口V3.0
token实现动态的传递
跨文件多接口多参数传递设计
1、脚本中获取token
登录接口中增加了token属性
初始时为空
调用登录接口成功后进行赋值
2、在获取用户信息接口的py文件中,要先引入对应的登录接口类
from login_model_interface.login_interface.loginV5 import login_interface
3、实例化对象调用类中的方法,获取token
4、把动态的token进行传入
登录接口测试V5.0代码
# ********************************************************************************
# 登录接口V5.0脚本实现
# 功能:1、调用图片验证码接口获取uuid
# 2、将获取到的uuid传入登录接口中
# 3、分别对两个功能进行面向对象封装
# 修改内容: 增加token属性,为其他接口提供验证
# ********************************************************************************
# 导入类库
import requests
import json
# 定义一个类:包含两个方法和一个属性
class login_interface():
# 定义一个属性
def __init__(self):
self.uuid=''
#增加token属性
self.token=''
#方法1:获取验证码对应的uuid
def getuuid(self):
# 取验证码图片接口uuid
# 定义获取验证码接口地址
CaptchaURL = 'https://svr-6-9009.share.51env.net/prod-api/captchaImage'
# 发送请求
response = requests.get(CaptchaURL).json()
# 获取uuid
# print(type(response))
# 全局uuid引用说明
# global uuid
self.uuid = response['uuid']
# print(self.uuid)
# return uuid
# 执行登录接口的测试
def login_test(self):
# 定义接口地址
url = "https://svr-6-9009.share.51env.net/prod-api/login"
# 定义接口参数
# print("登录方法中", self.uuid)
userinfo = {
"username": "admin",
"password": "admin123",
"code": "51testing",
"uuid": self.uuid
}
# print(type(userinfo))
# 将字典类型转化为json类型
userjson = json.dumps(userinfo)
# print(type(userjson))
# 发送接口请求
response = requests.post(url, data=userjson, headers={"Content-Type": "application/json"}).json()
# print(response)
# print(response["msg"])
# 对比实际和预期结果
if response["msg"] == '操作成功' and response["username"] == 'admin':
print("登录接口成功")
self.token=response["token"]
else:
print("登录接口失败")
# 进行实例化调用
if __name__ == '__main__':
#实例化对象
loginObj=login_interface()
#调用其中的方法
loginObj.getuuid()
loginObj.login_test()
print("token的值",loginObj.token)
获取登录用户信息接口V3.0
# **********************************************************
# 获取登录用户信息接口V3.0脚本实现
# 功能:常量参数传递
# 问题1:引用登录接口类
# 问题2:运行登录接口类,并获取最新的token
# 问题3:将获取到的最新的token传入获取用户信息接口
# ***********************************************************
# 引入登录接口类
from login_model_interface.login_interface.loginV5 import login_interface
# 导入类库
# **********************************************************
# 获取登录用户信息接口V2.0脚本实现
# 功能:常量参数传递
# 问题1:assert与异常处理try的结合技术
# ***********************************************************
# 导入类库
import requests
#**************************************************
# 实例化调用登录类,获取token属性
obJLogin=login_interface()
# 执行登录类中对应的两个接口方法
obJLogin.getuuid()
obJLogin.login_test()
token=obJLogin.token
print(token)
# 定义接口地址
url='https://svr-6-9009.share.51env.net/prod-api/getInfo'
# 定义token参数
# token="eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImQ2NDM0NTliLTliYWItNDcyNy1hMmEzLTdiY2Q1MmFjZWUwNiJ9.8OJdTrsT05qQdJDyqjjuY0BOeTi9CefAlJyr3iX9eGo8jlSKPw1DMsMcgjcgNtSNR2YKCaqeZSW67YilKW1vtg"
header={'Authorization':'Bearer '+token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
print(response)
# 进行检查点的验证
# 提取msg
msg=response['msg']
print(msg)
# 通过assert断言进行msg的验证
# 提取电话
phone=response['user']['phonenumber']
print(phone)
# 提取邮箱
email=response['user']['email']
print(email)
#提取用户名
username=response['user']['userName']
print(username)
#对assert进行异常捕获
try:
assert msg == '操作成功'
assert phone=='13800138000'
assert email=='13800138000@139.com'
assert username=='admin'
except:
print("获取用户信息接口测试失败")
else:
print("获取用户信息接口测试成功")
4.3.3 获取登录用户信息接口V4.0
采用面向对象的方法,获取登录用户信息接口。
# **********************************************************
# 获取登录用户信息接口V3.0脚本实现
# 功能:常量参数传递
# 问题1:引用登录接口类
# 问题2:运行登录接口类,并获取最新的token
# 问题3:用面向对象的方法将获取到的最新的token传入获取用户信息接口
# ***********************************************************
# 引入登录接口类
from login_model_interface.login_interface.loginV5 import login_interface
# 导入类库
import requests
class GetUserInfo():
def __init__(self):
self.token=''
def get_token(self):
#**************************************************
# 实例化调用登录类,获取token属性
obJLogin=login_interface()
# 执行登录类中对应的两个接口方法
obJLogin.getuuid()
obJLogin.login_test()
self.token=obJLogin.token
print(self.token)
def get_userinfo(self):
# 定义接口地址
url='https://svr-6-9009.share.51env.net/prod-api/getInfo'
# 定义token参数
# token="eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6ImQ2NDM0NTliLTliYWItNDcyNy1hMmEzLTdiY2Q1MmFjZWUwNiJ9.8OJdTrsT05qQdJDyqjjuY0BOeTi9CefAlJyr3iX9eGo8jlSKPw1DMsMcgjcgNtSNR2YKCaqeZSW67YilKW1vtg"
header={'Authorization':'Bearer '+self.token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
print(response)
# 进行检查点的验证
# 提取msg
msg=response['msg']
# print(msg)
# 通过assert断言进行msg的验证
# 提取电话
phone=response['user']['phonenumber']
# print(phone)
# 提取邮箱
email=response['user']['email']
# print(email)
# 提取用户名
username=response['user']['userName']
# print(username)
#对assert进行异常捕获
try:
assert msg == '操作成功'
assert phone=='13800138000'
assert email=='13800138000@139.com'
assert username=='admin'
except:
print("获取用户信息接口测试失败")
else:
print("获取用户信息接口测试成功")
if __name__ == '__main__':
GetuserObj=GetUserInfo()
GetuserObj.get_token()
GetuserObj.get_userinfo()
输出结果:
登录接口成功
eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjFhNDU0Y2Y1LTkwY2YtNDE5ZC04NDZiLTc5Yjk2NWM3ZGMxMSJ9.JGEcVWSEiZJg8QBFpLIWob_QY6bHVODouYmK60zvjTHcO5ewx2xnoKN4pty70V_CtVdG-LaBXDESRRFc7p_BJg
{'msg': '操作成功', 'code': 200, 'permissions': ['*:*:*'], 'roles': ['admin'], 'user': {'searchValue': None, 'createBy': 'admin', 'createTime': '2021-11-11 18:35:24', 'updateBy': None, 'updateTime': None, 'remark': '管理员', 'params': {}, 'userId': 1, 'deptId': 100, 'userName': 'admin', 'nickName': '物业管理员', 'email': '13800138000@139.com', 'phonenumber': '13800138000', 'sex': '1', 'avatar': '', 'salt': None, 'status': '0', 'delFlag': '0', 'loginIp': '58.240.250.49, 192.168.8.11', 'loginDate': '2024-07-03T07:42:13.000+00:00', 'dept': {'searchValue': None, 'createBy': None, 'createTime': None, 'updateBy': None, 'updateTime': None, 'remark': None, 'params': {}, 'deptId': 100, 'parentId': 0, 'ancestors': None, 'deptName': '润乐飞科技', 'orderNum': '0', 'leader': '若依', 'phone': None, 'email': None, 'status': '0', 'delFlag': None, 'parentName': None, 'children': []}, 'roles': [{'searchValue': None, 'createBy': None, 'createTime': None, 'updateBy': None, 'updateTime': None, 'remark': None, 'params': {}, 'roleId': 1, 'roleName': '超级管理员', 'roleKey': 'admin', 'roleSort': '1', 'dataScope': '1', 'menuCheckStrictly': False, 'deptCheckStrictly': False, 'status': '0', 'delFlag': None, 'flag': False, 'menuIds': None, 'deptIds': None, 'admin': True}], 'roleIds': None, 'postIds': None, 'roleId': None, 'admin': True}}
获取用户信息接口测试成功
5、获取左侧导航栏信息接口
5.1 接口测试分析
1、接口分析要素:接口地址、接口类型、接口参数、接口返回值
接口地址、接口类型是GET请求
接口参数:无
接口返回值:包含左侧菜单栏信息
接口返回有权限父节点的内容和权限子节点的内容
2、测试用例设计
包含完整性:个数不能少
准确性:内容是正确的;与登录的用户所拥有的权限相一致
难点:
a).要设计多少种用户的权限类型?
保证测试覆盖率
尽量不要漏测
b)测试用例设计方法
等价类边界值:
全部权限
admin管理员
只有唯一的一个权限
每个父节点下都选择一个子节点
1、构造新的预期测试数据结果文件
2、按照预期的测试结果文件在系统中添加相应对等的角色
3、添加对应的测试用户
4、用户进行登录,获取接口的实际返回结果
选择部分权限
每个父节点可以选1-2个子节点
5.2 接口冒烟测试
返回成功
5.3 接口脚本研发
共性
1、定义接口地址
2、定义接口参数
3、发送接口请求
4、获取接口实际返回结果
5、将实际结果和预期结果进行比对,给出测试结论
5.3.1 获取左侧导航栏信息接口V1.0
常量数据,跑通,解决相关的技术问题
# **********************************************************
# 获取用户左侧导航栏信息接口V1.0脚本实现
# 功能:常量参数传递
#
# ***********************************************************
# 导入类库
import requests
import csv
# 定义接口地址
url='https://svr-6-9009.share.51env.net/prod-api/getRouters'
# 定义token参数
token="eyJhbGciOiJIUzUxMiJ9.eyJsb2dpbl91c2VyX2tleSI6IjIwYjUyNzc1LTFhNjMtNDZmMS1iMTcwLTFjMTkzMjE2M2JlZCJ9.93mByisSSCLS_Xq6sVOWiX6zUdzmyBD_Qiy6xTWpnIGoQ_qF5uAsvwkEHwrVJwr52jII0Xe98dtA4adhic3Lkw"
header={'Authorization':'Bearer '+token}
# 发送请求并获取
response=requests.get(url,headers=header).json()
print(response)
print(type(response))
# 进行检查点的验证
#对权限的父子节点进行验证
#1、对父节点进行验证
#1.1 提取接口实际返回结果中的父节点列表
# 定义一个父节点的list
parentList=[]
for i in range(0,7):
parentmenu=response['data'][i]['meta']['title']
# print(parentmenu)
parentList.append(parentmenu)
# parentnum=len(parentList)
print("实际返回父节点内容",parentList)
#1.2 提取预期csv文件中父节点列表
#试验4:从csv文件获取父节点子节点的内容存入List中
#列表初始化
csvparentList=[]
#1、打开csv文件
csvfile=open('rolelist.csv','r')
#2、读取相关数据
rows=csv.reader(csvfile)
#跳过首行
header=next(rows)
#通过循环读取
for row in rows:
# print(row[0])
csvparentList.append(row[0])
# print(csvparentList)
print("预期返回父节点内容",list(set(csvparentList)))
#1.3 进行两者的比对,给出测试结论
#判断两个列表是否相等
result=(csvparentList.sort()==parentList.sort())
print(result)
if result==True:
print("实际结构和预期结果的父节点内容以及个数完全相同")
else:
print("实际结构和预期结果的父节点内容以及个数有相同")
#2.提取子节点内容进行比对
#2.1 提取接口实际返回结果中的子节点列表
# 提取子节点的内容存入list中
childList=[]
for m in range(0,7):
# print(m+1)
childtmp = []
for j in range(0,9):
try:
childmenu=response['data'][m]['children'][j]['meta']['title']
except:
break
else:
# print(childmenu)
childtmp.append(childmenu)
# print(childtmp)
childList.append(childtmp)
print("实际接口返回子节点的列表嵌套",childList)
#2.2 提取预期csv文件中子节点列表
import csv
# 定义一个列表
listall=[]
listchild=[]
listchildresult=[]
count=[4,2,4,8,9,5,3]
# 从csv文件中提取所有的子节点存入一个列表中
# 打开csv文件
csvfile=open('rolelist.csv','r')
rows=csv.reader(csvfile)
# 跳过首行
next(rows)
#提取子节点,放入新的列表
for row in rows:
# print(row[1])
listall.append(row[1])
print(listall)
# 将listall中的内容按照count进行数量分组
k=0
for i in count:
# print(i)
listchild = []
for j in range(0,i):
# print(j)
# 按照分组个数要求提取listall中的内容,插入到新的列表中。
listchild.append(listall[k])
k=k+1
listchildresult.append(listchild)
print("预期结果子节点的列表嵌套",listchildresult)
#2.3 进行两者的比对,给出测试结论
childresult=childList.sort()==listchildresult.sort()
print(childresult)
if childresult==True:
print("实际结构和预期结果的子节点内容以及个数完全相同")
else:
print("实际结构和预期结果的子节点内容以及个数不完全相同")
# 给出整体的接口测试结论
if result==True and childresult==True:
print("getRouters接口整体测试验证通过")
else:
print("getRouters接口整体测试验证失败")
输出结果:
{'msg': '操作成功', 'code': 200, 'data': [{'name': 'Payment', 'path': '/payment', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '物业收费', 'icon': 'shopping', 'noCache': False, 'link': None}, 'children': [{'name': 'Housepay', 'path': 'housepay', 'hidden': False, 'component': 'pms/housepay', 'meta': {'title': '商铺综合收费', 'icon': 'money', 'noCache': False, 'link': None}}, {'name': 'Temppay', 'path': 'temppay', 'hidden': False, 'component': 'pms/paymenttemp', 'meta': {'title': '临时收费', 'icon': 'form', 'noCache': False, 'link': None}}, {'name': 'Deposit', 'path': 'deposit', 'hidden': False, 'component': 'pms/paymentdeposit', 'meta': {'title': '押金管理', 'icon': 'dict', 'noCache': False, 'link': None}}, {'name': 'Pre', 'path': 'pre', 'hidden': False, 'component': 'pms/paymentpre', 'meta': {'title': '预存款管理', 'icon': 'documentation', 'noCache': False, 'link': None}}]}, {'name': 'Fee', 'path': '/fee', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '收费数据管理', 'icon': 'build', 'noCache': False, 'link': None}, 'children': [{'name': 'Meter', 'path': 'meter', 'hidden': False, 'component': 'pms/paymentmeter', 'meta': {'title': '抄表数据管理', 'icon': 'time-range', 'noCache': False, 'link': None}}, {'name': 'Bill', 'path': 'bill', 'hidden': False, 'component': 'pms/paymentbill', 'meta': {'title': '商铺收费数据', 'icon': 'edit', 'noCache': False, 'link': None}}]}, {'name': 'Config', 'path': '/config', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '基础数据管理', 'icon': 'cascader', 'noCache': False, 'link': None}, 'children': [{'name': 'Configfeeitem', 'path': 'configfeeitem', 'hidden': False, 'component': 'pms/configfeeitem', 'meta': {'title': '收费项管理', 'icon': 'list', 'noCache': False, 'link': None}}, {'name': 'Pms/confighouseblock', 'path': 'pms/confighouseblock', 'hidden': False, 'component': 'pms/confighouseblock', 'meta': {'title': '商业区管理', 'icon': 'example', 'noCache': False, 'link': None}}, {'name': 'Config/house', 'path': 'config/house', 'hidden': False, 'component': 'pms/confighouse', 'meta': {'title': '商铺管理', 'icon': 'component', 'noCache': False, 'link': None}}, {'name': 'Config/contract', 'path': 'config/contract', 'hidden': False, 'component': 'pms/confighousecontract', 'meta': {'title': '商铺租售', 'icon': 'switch', 'noCache': False, 'link': None}}]}, {'name': 'Statistics', 'path': '/statistics', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '统计报表', 'icon': 'monitor', 'noCache': False, 'link': None}, 'children': [{'name': 'DailyReport', 'path': 'dailyReport', 'hidden': False, 'component': 'statistics/daily', 'meta': {'title': '收费日报表', 'icon': 'job', 'noCache': False, 'link': None}}, {'name': 'DailyFee', 'path': 'dailyFee', 'hidden': False, 'component': 'statistics/dailyFee', 'meta': {'title': '收费项汇总表', 'icon': 'excel', 'noCache': False, 'link': None}}, {'name': 'PayLog', 'path': 'payLog', 'hidden': False, 'component': 'statistics/paylog', 'meta': {'title': '收费明细表', 'icon': 'date-range', 'noCache': False, 'link': None}}, {'name': 'NextFee', 'path': 'nextFee', 'hidden': False, 'component': 'statistics/nextFee', 'meta': {'title': '待生成的费用', 'icon': 'skill', 'noCache': False, 'link': None}}, {'name': 'PreAccount', 'path': 'preAccount', 'hidden': False, 'component': 'statistics/preAccount', 'meta': {'title': '预收款余额', 'icon': 'druid', 'noCache': False, 'link': None}}, {'name': 'Fee', 'path': 'fee', 'hidden': False, 'component': 'statistics/payment', 'meta': {'title': '收费统计', 'icon': 'chart', 'noCache': False, 'link': None}}, {'name': 'UserFee', 'path': 'userFee', 'hidden': False, 'component': 'statistics/payment/house', 'meta': {'title': '收费统计(商铺)', 'icon': 'chart', 'noCache': False, 'link': None}}, {'name': 'Overdue', 'path': 'overdue', 'hidden': False, 'component': 'statistics/overdue', 'meta': {'title': '欠费数据', 'icon': 'server', 'noCache': False, 'link': None}}]}, {'name': 'System', 'path': '/system', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '系统管理', 'icon': 'system', 'noCache': False, 'link': None}, 'children': [{'name': 'User', 'path': 'user', 'hidden': False, 'component': 'system/user/index', 'meta': {'title': '用户管理', 'icon': 'user', 'noCache': False, 'link': None}}, {'name': 'Role', 'path': 'role', 'hidden': False, 'component': 'system/role/index', 'meta': {'title': '角色管理', 'icon': 'peoples', 'noCache': False, 'link': None}}, {'name': 'Menu', 'path': 'menu', 'hidden': False, 'component': 'system/menu/index', 'meta': {'title': '菜单管理', 'icon': 'tree-table', 'noCache': False, 'link': None}}, {'name': 'Dept', 'path': 'dept', 'hidden': False, 'component': 'system/dept/index', 'meta': {'title': '部门管理', 'icon': 'tree', 'noCache': False, 'link': None}}, {'name': 'Post', 'path': 'post', 'hidden': False, 'component': 'system/post/index', 'meta': {'title': '岗位管理', 'icon': 'post', 'noCache': False, 'link': None}}, {'name': 'Dict', 'path': 'dict', 'hidden': False, 'component': 'system/dict/index', 'meta': {'title': '字典管理', 'icon': 'dict', 'noCache': False, 'link': None}}, {'name': 'Config', 'path': 'config', 'hidden': False, 'component': 'system/config/index', 'meta': {'title': '参数设置', 'icon': 'edit', 'noCache': False, 'link': None}}, {'name': 'Notice', 'path': 'notice', 'hidden': False, 'component': 'system/notice/index', 'meta': {'title': '通知公告', 'icon': 'message', 'noCache': False, 'link': None}}, {'name': 'Log', 'path': 'log', 'hidden': False, 'redirect': 'noRedirect', 'component': 'ParentView', 'alwaysShow': True, 'meta': {'title': '日志管理', 'icon': 'log', 'noCache': False, 'link': None}, 'children': [{'name': 'Operlog', 'path': 'operlog', 'hidden': False, 'component': 'monitor/operlog/index', 'meta': {'title': '操作日志', 'icon': 'form', 'noCache': False, 'link': None}}, {'name': 'Logininfor', 'path': 'logininfor', 'hidden': False, 'component': 'monitor/logininfor/index', 'meta': {'title': '登录日志', 'icon': 'logininfor', 'noCache': False, 'link': None}}]}]}, {'name': 'Monitor', 'path': '/monitor', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '系统监控', 'icon': 'monitor', 'noCache': False, 'link': None}, 'children': [{'name': 'Online', 'path': 'online', 'hidden': False, 'component': 'monitor/online/index', 'meta': {'title': '在线用户', 'icon': 'online', 'noCache': False, 'link': None}}, {'name': 'Job', 'path': 'job', 'hidden': False, 'component': 'monitor/job/index', 'meta': {'title': '定时任务', 'icon': 'job', 'noCache': False, 'link': None}}, {'name': 'Druid', 'path': 'druid', 'hidden': False, 'component': 'monitor/druid/index', 'meta': {'title': '数据监控', 'icon': 'druid', 'noCache': False, 'link': None}}, {'name': 'Server', 'path': 'server', 'hidden': False, 'component': 'monitor/server/index', 'meta': {'title': '服务监控', 'icon': 'server', 'noCache': False, 'link': None}}, {'name': 'Cache', 'path': 'cache', 'hidden': False, 'component': 'monitor/cache/index', 'meta': {'title': '缓存监控', 'icon': 'redis', 'noCache': False, 'link': None}}]}, {'name': 'Tool', 'path': '/tool', 'hidden': False, 'redirect': 'noRedirect', 'component': 'Layout', 'alwaysShow': True, 'meta': {'title': '系统工具', 'icon': 'tool', 'noCache': False, 'link': None}, 'children': [{'name': 'Build', 'path': 'build', 'hidden': False, 'component': 'tool/build/index', 'meta': {'title': '表单构建', 'icon': 'build', 'noCache': False, 'link': None}}, {'name': 'Gen', 'path': 'gen', 'hidden': False, 'component': 'tool/gen/index', 'meta': {'title': '代码生成', 'icon': 'code', 'noCache': False, 'link': None}}, {'name': 'Swagger', 'path': 'swagger', 'hidden': False, 'component': 'tool/swagger/index', 'meta': {'title': '系统接口', 'icon': 'swagger', 'noCache': False, 'link': None}}]}]}
<class 'dict'>
实际返回父节点内容 ['物业收费', '收费数据管理', '基础数据管理', '统计报表', '系统管理', '系统监控', '系统工具']
预期返回父节点内容 ['系统管理', '系统工具', '统计报表', '物业收费', '收费数据管理', '系统监控', '基础数据管理']
True
实际结构和预期结果的父节点内容以及个数完全相同
实际接口返回子节点的列表嵌套 [['商铺综合收费', '临时收费', '押金管理', '预存款管理'], ['抄表数据管理', '商铺收费数据'], ['收费项管理', '商业区管理', '商铺管理', '商铺租售'], ['收费日报表', '收费项汇总表', '收费明细表', '待生成的费用', '预收款余额', '收费统计', '收费统计(商铺)', '欠费数据'], ['用户管理', '角色管理', '菜单管理', '部门管理', '岗位管理', '字典管理', '参数设置', '通知公告', '日志管理'], ['在线用户', '定时任务', '数据监控', '服务监控', '缓存监控'], ['表单构建', '代码生成', '系统接口']]
['商铺综合收费', '临时收费', '押金管理', '预存款管理', '抄表数据管理', '商铺收费数据', '收费项管理', '商业区管理', '商铺管理', '商铺租售', '收费日报表', '收费项汇总表', '收费明细表', '待生成的费用', '预收款余额', '收费统计', '收费统计(商铺)', '欠费数据', '用户管理', '角色管理', '菜单管理', '部门管理', '岗位管理', '字典管理', '参数设置', '通知公告', '日志管理', '在线用户', '定时任务', '数据监控', '服务监控', '缓存监控', '表单构建', '代码生成', '系统接口']
预期结果子节点的列表嵌套 [['商铺综合收费', '临时收费', '押金管理', '预存款管理'], ['抄表数据管理', '商铺收费数据'], ['收费项管理', '商业区管理', '商铺管理', '商铺租售'], ['收费日报表', '收费项汇总表', '收费明细表', '待生成的费用', '预收款余额', '收费统计', '收费统计(商铺)', '欠费数据'], ['用户管理', '角色管理', '菜单管理', '部门管理', '岗位管理', '字典管理', '参数设置', '通知公告', '日志管理'], ['在线用户', '定时任务', '数据监控', '服务监控', '缓存监控'], ['表单构建', '代码生成', '系统接口']]
True
实际结构和预期结果的子节点内容以及个数完全相同
getRouters接口整体测试验证通过
Process finished with exit code 0
5.4 脚本研发中遇到的各种技术问题
框内圈中的是父节点,父节点的下一级是子节点。
核心难点
1、父节点验证点
1.1 接口返回的结果中父节点的总数要与对一个登录用户权限所拥有的功能权限总数相匹配。
1.2 接口返回的结果中父节点的内容要与对应登录用户权限所拥有的功能权限内容相匹配。
2、子节点验证点。
2.1 接口返回的结果中子节点的总数要与对一个登录用户权限所拥有的子节点功能权限总数相匹配。
2.2 接口返回的结果中子节点的内容要与对应登录用户权限所拥有的子节点功能权限内容相匹配。
测开工程师和手工测试工程师是有很大的区别的,大家一定要善于使用脚本来提高我们的工作效率,提高准确率。能用代码去干的,不要用手工去干。
要有一个好的习惯,就是让你的脑子里有画面。大家不一定去做PPT,但是大家在思考问题关联的时候,脑子里得出现类似与PPT的画面。这样的话思考力就可以很大的去提升了。
5.4.1 目标1提取接口实际返回结果
1、提取父节点
多个字典key获取数据时,报错。
问题产生原因:接口返回结果字典内嵌了列表继续内嵌字典
解决方案:数据类型是复合型的,逐层进行类型的确认。
代码实现:
parentmenu=response['data'][0]['meta']['title]
父节点是多个提取时代码实现:使用循环结构
for i in range(0,7):
parentmenu=response['data'][i]['meta']['title']
print(parentmenu)
2、提取子节点
代码关键内容
childmenu=response['data'][0]['children'][j]['meta']['title']
子节点是多个,使用循环结构,又发现新问题,子节点个数不固定,最大值不统一
解决方法:使用try异常处理
for m in range(0,7):
print(m + 1)
for j in range(0,9):
try:
childmenu=response['data'][m]['children'][j]['meta']['title']
except:
break
else:
print(childmenu)
5.4.2 目标2提取接口实际返回结果
1、提取预期结果的返回值
问题1:
预期结果在哪里体现?预期结果值多个+多类+多变
解决方案:一个用户对应一个预期结果文件
问题2:
文件如何构造?通过脚本来构造,前提,接口实际返回结果是正确的
问题3:如何从CSV中提取预期结果的文件
类型选择,父节点,列表;子节点,列表嵌套
2、语法实现
a)读取csv文件
import csv
打开文件
csvfile=open('rolelist.csv','r')
获取文件句柄
rows=csv.reader(csvfile)
跳过首行
header=next(rows)
循环读取文件内容
for row in rows:
#列号从0开始,逐行读取
print(row[0])
b) 读取父节点,并且转化为list
csvparentList.append(row[0])
c) 读取子节点,并转化为list嵌套
为了简化脚本的难度
count=[4,2,4,8,9,5,3]
先将所有子节点从文件提取出来,放入一个list列表中
for row in rows:
listall.append(row[1])
按照count中对应的子节点的个数,逐步提取并放入列表嵌套
k=0 #控制整体下标的变化
for i in count:
listchild = []
for j in range(0,i):
# print(j)
# 按照分组个数要求提取listall中的内容,插入到新的列表中。
listchild.append(listall[k])
k=k+1
listchildresult.append(listchild)
print("预期结果子节点的列表嵌套",listchildresult)
5.4.3 进行实际结果和预期结果的比对
==判断
a==b的意思是判断a是否等于b
列表注意
必须内容一致,顺序一致
sort()排序方法
r=lista.sort()==listb.sort() #这句代码的意思是排序后的a列表和b列表进行对比,为真把True给r,为假把False赋值给r。
愿每个测试都能顺利转为测试开发,提高职业技能,成为前1%的存在,为社会创造更大的价值,为公司节约更多的成本,为自己和家庭谋求更高的收入,所有人不受职业年龄限制,越老越吃香,直至财富自由;愿测试技术越来越进步,软件质量进一步得到提高,效率提高。愿祖国更加美好,人民更加幸福。多喜乐,常安宁。