接口自动化(四)接口关联的封装、统一请求封装、数据驱动、数据驱动断言
一、接口文档
https://api.apiopen.top/swagger/index.html#/
http://httpbin.org/
二、接口关联封装
目录结构:

test_api.py
import requests
class TestRequest():
#全局变量(类变量),通过类名调用
access_token="" #定义全局变量access_token
sess=requests.session() #定义全局变量sess=requests.session()
# 获取token
def test_token(self):
url = "https://api.apiopen.top/api/login"
headers = {
"accept": "application/json",
"Content-Type": "application/json"
}
body = {
"account": "1195528321@qq.com",
"password": "000000"
}
res=TestRequest.sess.request(method="post",url=url,headers=headers,json=body) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json()["result"]["token"])
TestRequest.access_token=res.json()["result"]["token"] #将token的值赋给全局变量access_token
# 文件上传
def test_file(self):
url = "http://httpbin.org/post"
headers = {
"accept": "application/json",
"Content-Type": "multipart/form-data"
}
files = {
"file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb") # rb:转换为只读的二进制流
}
res = TestRequest.sess.request(method="post", url=url, headers=headers,files=files) # 将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.status_code)
test_user.py #由于在test_api.py定义了全局变量,而test.user.py想使用这些全局变量就得导入test_api.py模块下的类
import requests
from testcases.test_api import TestRequest # 导入testcases包下的test_api模块下的TestRequest类
class TestUser():
# 查询用户信息
def test_user(self):
url = "https://api.apiopen.top/api/getUserInfo"
headers = {
"accept": "application/json",
"token": TestRequest.access_token #使用TestRequest类的全局变量access_token
}
res = requests.request(method="get", url=url,headers=headers)
print(res.json())
pytest.ini
[pytest]
addopts =
testpaths = ./
python_files = test_*.py
python_classes = Test*
python_functions = test
run.py
import pytest
if __name__ == '__main__':
pytest.main()
执行结果:
testcases\test_api.py .. [ 40%] #这里2个“.”表示执行了2个用例
testcases\test_user.py ... [100%] #这里3个“.”表示执行了3个用例
============================== 5 passed in 4.82s ==============================
为什么test_user.py中明明1个用例却执行了3个用例呢?这是由于类之间的继承特征导致的:test_user.py调用test_api.py下的类的时候把test_api.py类下的2个用例也执行了一遍
如何避免这种错误呢,可以去掉类中全局变量,用YAML文件代为保存全局变量:
目录结构:#新增一个空文件extract.yaml,新增一个公共函数目录common和公共模块yaml_util.py,然后test.api.py调用yaml_util.py模块将产生的全局变量access_token存储到extract.yaml,最后test_user.py就可以在extract.yaml中提取使用access_token了

yaml_util.py
import yaml #需要安装模块:pip install pyyaml
import os
#读取
def read_yaml(key):
with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f: #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
value=yaml.load(stream=f,Loader=yaml.FullLoader) #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
return value[key] #value是字典类型的,可以通过关键字方式输出值
#写入
def write_yaml(data):
with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
yaml.dump(data,stream=f,allow_unicode=True) #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式
#清空
def clear_yaml():
with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
f.truncate() #清空文件内容
test_api.py #1、导入yaml_util.py模块 2、删除全局变量access_token 3、修改test_token用例中保存token的方式(存到文件中)
import requests
from common.yaml_util import write_yaml #导入公共函数中“写入”yaml方法
class TestRequest():
#全局变量(类变量),通过类名调用
sess=requests.session() #定义全局变量sess=requests.session()
# 获取token
def test_token(self):
url = "https://api.apiopen.top/api/login"
headers = {
"accept": "application/json",
"Content-Type": "application/json"
}
body = {
"account": "1195528321@qq.com",
"password": "000000"
}
res=TestRequest.sess.request(method="post",url=url,headers=headers,json=body) #将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.json()["result"]["token"])
write_yaml({"access_token": res.json()["result"]["token"]}) # 将{"access_token":token值}键值对写入extract.yaml文件
# 文件上传
def test_file(self):
url = "http://httpbin.org/post"
headers = {
"accept": "application/json",
"Content-Type": "multipart/form-data"
}
files = {
"file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb") # rb:转换为只读的二进制流
}
res = TestRequest.sess.request(method="post", url=url, headers=headers,files=files) # 将所有的requests都替换为requests.session()就相当于都在一个会话中了
print(res.status_code)
test_user.py #1、删除导入test_api.py中全局变量的方法 2、导入yaml_util.py模块 3、修改test_edit_user用例中获取token的方式(从文件中读取)
import requests
from common.yaml_util import read_yaml #导入公共函数中“读取”yaml方法
class TestUser():
# 查询用户信息
def test_user(self):
url = "https://api.apiopen.top/api/getUserInfo"
headers = {
"accept": "application/json",
"token": read_yaml("access_token") #读取extract.yaml文件中key为access_token对应的值
}
res = requests.request(method="get", url=url,headers=headers)
print(res.json())
run.py
import pytest
if __name__=='__main__':
pytest.main()
pytest.ini
[pytest]
addopts =
testpaths = ./
python_files = test_*.py
python_classes = Test*
python_functions = test
执行结果:
testcases\test_api.py .. [ 66%] #执行了2条用例
testcases\test_user.py . [100%] #执行了1条用例
============================== 3 passed in 5.71s ==============================
extract.yaml #生成了一条token的值
access_token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOjg5NCwiaWQiOjg5NCwiY3JlYXRlZEF0IjoiMjAyMy0wOC0wMyAwMToxMTowOSIsInVwZGF0ZWRBdCI6IjIwMjMtMDgtMDMgMDE6MTE6MDkiLCJkZWxldGVkQXQiOm51bGwsImFjY291bnQiOiIxMTk1NTI4MzIxQHFxLmNvbSIsImxldmVsIjowLCJleHAiOjE2OTc3MzA1NzgsImlzcyI6ImFwaV9vcGVuIiwibmJmIjoxNjk3MTI0Nzc4fQ.98qISsPB0YJDVPx86yyrybsy8aR0m94O8K6t-NWpLYA
以上代码仍有问题,由于write.yaml()选择追加的方式写入数据,多次执行后extract.yaml文件中会有多个access_token:[value]键值对,到时候read.yaml()取值的时候可能会报错,解决办法是增加前后置方法:在每次写入文件前清空extract.yaml文件
#演示:
目录结构:#增加前后置夹具

conftest.py
import pytest
from common.yaml_util import clear_yaml #导入公共函数中“清空”yaml方法
#在所有接口请求之前执行
@pytest.fixture(scope='package',autouse=True) #作用域是包,即testcases目录,并且自动执行
def clear_extract():
clear_yaml() #这是一个前置
三、统一请求封装
如果test_user.py中用例需要test_api.py中用例的cookie怎么办呢?可以调用test_api.py中全局变量sess=requests.session()使两个文件中的用例处于同一个会话(session),但是这样同样会因为类之间继承的问题导致test_api.py被调用时再执行一次,怎么解决呢,可以使用统一请求封装
将统一会话请求封装到一个模块中,然后所有测试用例去调用这个模块中的统一请求方法
目录结构:#新增文件

request_util.py
import requests
class RequestUtil():
#全局变量(类变量),通过类名调用
sess=requests.session()
def send_request(self,method,url,**kwargs):
method=str(method).lower() #防止用户在传入get|post写为大写,可以用lower()转化为小写
res=RequestUtil.sess.request(method=method,url=url,**kwargs)
return res
test_api.py #1、导入统一请求模块 2、删除sess=requests.session()全局变量 3、改用统一请求模块发送接口请求 4、因为使用了统一请求,因此无需import requests
from common.yaml_util import write_yaml #导入公共函数中“写入”yaml方法
from common.request_util import RequestUtil #导入公共函数中”统一session请求”方法
class TestRequest():
# 获取token
def test_token(self):
url = "https://api.apiopen.top/api/login"
headers = {
"accept": "application/json",
"Content-Type": "application/json"
}
body = {
"account": "1195528321@qq.com",
"password": "000000"
}
res=RequestUtil().send_request(method="post",url=url,headers=headers,json=body) #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
print(res.json()["result"]["token"])
write_yaml({"access_token": res.json()["result"]["token"]}) # 将{"access_token":token值}键值对写入extract.yaml文件
# 文件上传
def test_file(self):
url = "http://httpbin.org/post"
headers = {
"accept": "application/json",
"Content-Type": "multipart/form-data"
}
files = {
"file": open(r"C:\Users\lasou\Desktop\11.jpg", "rb") # rb:转换为只读的二进制流
}
res = RequestUtil().send_request(method="post", url=url, headers=headers,files=files) #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
print(res.status_code)
test_user.py #1、导入统一请求模块 2、改用统一请求模块发送接口请求 4、因为使用了统一请求,因此无需import requests
from common.yaml_util import read_yaml #导入公共函数中“读取”yaml方法
from common.request_util import RequestUtil #导入公共函数中”统一session请求”方法
class TestUser():
# 查询用户信息
def test_user(self):
url = "https://api.apiopen.top/api/getUserInfo"
headers = {
"accept": "application/json",
"token": read_yaml("access_token") #读取extract.yaml文件中key为access_token对应的值
}
res = RequestUtil().send_request(method="get", url=url,headers=headers) #RequestUtil在request_util.py中是个类,因此调用其中方法时需要先实例化RequestUtil(RequestUtil后面得带上括号)
print(res.json())
#其余文件不变
执行结果:
testcases\test_api.py .. [ 66%]
testcases\test_user.py . [100%]
============================== 3 passed in 5.51s ==============================
四、数据驱动
实际测试环境使用pytest的时候还需要建立数据驱动,所谓数据驱动就是把test_api.py、test_user.py中test_token、test_user等方法中写的method、url、headers、json等变量的内容封装到一个yaml文件中去调用,而不是直接写死到方法中,这个yaml文件就是数据驱动
数据驱动需要用到装饰器@pytest.mark.parametrize()
语法:@pytest.mark.parametrize(args_name,args_value)
#args_name 参数名,字符串,可自定义名称
#args_value 参数值(list,tuple,字典列表,字典元组),有多少个值那么测试用例就会执行多少次(以"-"为分隔符计算有多少个值)
#YAML介绍:
yaml是一种数据格式,主要用于配置文件或者编写用例
yaml只有两种数据:
1.键值对
key:(空格)value
2.list
用一个“-”表示一个列表
操作yaml的第三方模块是pyyaml,安装方法:pip install pyyaml
目录结构:#1、新增文件test_api-test_token.yaml 2、为方便演示删除test_user.py

test_api-test_token.yaml
- #这个"-"是每组数据的分隔符
name: 获取接口统一鉴权码token #本组数据名称
request: #请求内容
method: post #请求方法
url: https://api.apiopen.top/api/login #请求url
headers: #请求头
accept: application/json
Content-Type: application/json
body: #请求体
account: 1195528321@qq.com
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None #断言,这里选择不使用
#name、request、validate是数据驱动最基本的内容,当然这三个基本内容的名称可自定义
yaml_util.py #增加一个读取数据驱动的方法read_testcases
import yaml #需要安装模块:pip install pyyaml
import os
#读取
def read_yaml(key):
with open(os.getcwd()+'/extract.yaml',mode='r',encoding='utf-8') as f: #os.getcwd()方法用于返回当前工作目录 #mode='a':追加|'w':覆盖|'r':只读 #encoding编码格式
value=yaml.load(stream=f,Loader=yaml.FullLoader) #yaml.load()读取yaml文件 #stream=f要读取的对象 #Loader=yaml.FullLoader全部提取
return value[key] #value是字典类型的,可以通过关键字方式输出值
#写入
def write_yaml(data):
with open(os.getcwd()+'/extract.yaml',mode='a',encoding='utf-8') as f:
yaml.dump(data,stream=f,allow_unicode=True) #yaml.dump()写入数据到yaml文件 #allow_unicode=True允许unicode方式
#清空
def clear_yaml():
with open(os.getcwd()+'/extract.yaml',mode='w',encoding='utf-8') as f:
f.truncate() #清空文件内容
#读取数据驱动
def read_testcases(yaml_name):
with open(os.getcwd()+'/testcases'+'/'+yaml_name,mode='r',encoding='utf-8') as f:
value=yaml.load(stream=f,Loader=yaml.FullLoader)
return value
test_api.py #为方便演示只保留一个用例 1、导入公共函数中“读取数据驱动”方法 2、导入pytest模块, 3、使用@pytest.mark.parametrize()获取请求参数
from common.yaml_util import write_yaml,read_testcases #导入公共函数中“写入”yaml方法,“读取数据驱动”方法
from common.request_util import RequestUtil #导入公共函数中”统一session请求”方法
import pytest
class TestRequest():
# 获取token
@pytest.mark.parametrize("args_name",read_testcases("test_api-test_token.yaml")) #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
def test_token(self,args_name):
url = args_name["request"]["url"] #从args_name中获取url
headers = args_name["request"]["headers"] #从args_name中获取请求头
body = args_name["request"]["body"] #从args_name中获取请求体
res=RequestUtil().send_request(method=args_name["request"]["method"],url=url,headers=headers,json=body) #从args_name中获取method
print(res.json()["result"]["token"])
write_yaml({"access_token": res.json()["result"]["token"]}) # 将{"access_token":token值}键值对写入extract.yaml文件
#其余文件不变
执行结果:
testcases\test_api.py . [100%]
============================== 1 passed in 0.22s ==============================
一个接口一般对应一个数据驱动yaml文件, 一个yaml可以以“-”为分隔符定义多组测试数据,@pytest.mark.parametrize可以根据有几组测试数据就把这个测试用例跑几遍
#演示
test_api-test_token.yaml
- #第1组数据
name: 获取接口统一鉴权码token #本组数据名称
request: #请求内容
method: post #请求方法
url: https://api.apiopen.top/api/login #请求url
headers: #请求头
accept: application/json
Content-Type: application/json
body: #请求体
account: 1195528321@qq.com
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None #断言,这里选择不使用
- #第2组数据
name: 获取接口统一鉴权码token #本组数据名称
request: #请求内容
method: post #请求方法
url: https://api.apiopen.top/api/login #请求url
headers: #请求头
accept: application/json
Content-Type: application/json
body: #请求体
account: 971045897@qq.com
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: None #断言,这里选择不使用
conftest.py #因为每跑一组数据就会写入一条token,因此前置作用域改为“函数”
import pytest
from common.yaml_util import clear_yaml #导入公共函数中“清空”yaml方法
#在所有接口请求之前执行
@pytest.fixture(scope='function',autouse=True) #作用域是函数,即testcases目录,并且自动执行
def clear_extract():
clear_yaml() #这是一个前置
#其余文件不变
执行结果: #可以看到test_token测试用例被执行了2次,在实际使用时还可以将yaml中第2组数据写一个错误的值以验证测试用例准确性
testcases\test_api.py .. [100%] #有两个“.”说明用例跑了两组数据
============================== 2 passed in 2.29s ==============================
五、数据驱动断言
断言assert用法:在python脚本中断言的作用是如果跟在assert后面条件为True继续执行下面内容,否则报错退出
#根据接口文档,返回状态码为200表示登录并成功获取token。因此可以加断言判断测试用例是否执行成功
目录结构:#不变
test_api-test_token.yaml
- #第1组数据
name: 获取接口统一鉴权码token #本组数据名称
request: #请求内容
method: post #请求方法
url: https://api.apiopen.top/api/login #请求url
headers: #请求头
accept: application/json
Content-Type: application/json
body: #请求体
account: 1195528321@qq.com
password: '000000' #000000加上引号表示是字符串,防止被识别成int,int只有一位0
validate: #断言
eq: {code: 200}
in: {code1: 200,code2: 2003}
test_api.py #1、增加从数据驱动中读取的两个变量val、li_val 2、增加两个断言
from common.yaml_util import write_yaml,read_testcases #导入公共函数中“写入”yaml方法,“读取数据驱动”方法
from common.request_util import RequestUtil #导入公共函数中”统一session请求”方法
import pytest
class TestRequest():
# 获取token
@pytest.mark.parametrize("args_name",read_testcases("test_api-test_token.yaml")) #args_value的值是read_testcases读取yaml中的内容 #args_name返回的是字典类型
def test_token(self,args_name):
url = args_name["request"]["url"] #从args_name中获取url
headers = args_name["request"]["headers"] #从args_name中获取请求头
body = args_name["request"]["body"] #从args_name中获取请求体
res=RequestUtil().send_request(method=args_name["request"]["method"],url=url,headers=headers,json=body) #从args_name中获取method
val=args_name["validate"]["eq"]["code"] #从数据驱动中读取数值200
li_val=[ args_name["validate"]["in"]["code1"],args_name["validate"]["in"]["code2"] ] #从数据驱动中读取列表[200,2003]
assert res.status_code == val #断言:判断返回的状态码是否等于200,是就下一步,不是就报错退出
assert res.status_code in li_val #断言:判断返回的状态码是否是列表[200,2003]中的一个,是就下一步,不是就报错退出
write_yaml({"access_token": res.json()["result"]["token"]}) # 将{"access_token":token值}键值对写入extract.yaml文件
#其余文件不变
执行结果:
testcases\test_api.py . [100%]
============================== 1 passed in 0.22s ==============================
这样一个简单的pytest测试框架就搭建完成了。
浙公网安备 33010602011771号