文章目录
- Requests库介绍
- Requests库安装
- Requests库使用
- 实际案例
- 添加日志功能
- 封装requests库
- 使用pytest装饰器实现参数化
- 通过读取Json文件实现参数化
- 通过读取Yaml文件实现参数化
- 通过读取Excel文件实现参数化
- 封装文件解析方法
- 测试报告
- Pytest-html报告
- Allure报告
- 补充:文件上传
关于Pytest和Allure如何使用请查看
此文章,这里不再介绍
Requests库介绍
requests库是Python第三方库,用于模拟浏览器向服务器发出请求,比如:常用的GET
,POST
,PUT
,DELETE
等请求,发起对应请求可以使用requests.get()……
,此方式更适合测试少量接口时使用,通常使用requests.request(method,url,**kwargs)
将请求方法作为参数,更便于做接口自动化,下文也将以此种方式进行介绍
Requests库安装
因为是Python第三方库,需要单独安装,方法如下
pip install requests # 通过官网安装,国内环境可能会下载失败,可以使用国内pip源
pip install requests -i https://pypi.tuna.tsinghua.edu.cn/simple # 临时使用清华pip源
或者永久更改pip源后再执行pip install requests
,执行以下命令将永久更改为清华pip源
pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple
Requests库使用
安装后需导入requests库使用,如下示例:
import requests # # 导入requests库
base_url = "http://192.166.66.24:8090" # 为方便测试,先定义个base_url,也可以跟路径写一起
login_url = base_url+"/api/admin/login" # 登录URL,通过base_url与路径拼接
login_data = {"username": "admin","password": "admin123","authcode": None} # 登录需要的入参数据
r_login = requests.request(method='post',url=login_url,json=login_data) # 发起post请求
print(r_login.status_code) # 打印响应状态码
print(r_login.content) # 打印原始响应体,即raw格式的
ck = r_login.content # 获取cookie值
token = r_login.json()['data']['access_token'] # 获取响应中的token值
print(token) # 打印token值
artlist_url = base_url+"/api/admin/posts" # 获取文章列表的URL
header={"Admin-Authorization":token} # 请求头信息,需要带上token值
r_artlist = requests.request("get",artlist_url,headers=header) # 发起get请求,method和url可省略参数名
print(r_artlist.json()) # 打印json格式的响应数据
print(r_artlist.text) # 打印字符串格式的响应数据
**requests.request(method,url,kwargs)参数说明
method:请求方法,如GET、POST、PUT、DELETE等请求
url:请求的URL地址
**kwargs:是一个可变的参数类型,在传实参时,以关键字参数的形式传入,python会自动解析为字典的形式,常用可选参数如下:
- params:字典或元组列表或字节,作为参数增加到url中
- data:字典,元组列表,字节或文件对象,作为post、put等请求的参数
- json:JSON格式的数据,作为post、put等请求的参数
- headers:字典类型, HTTP请求头信息
- cookies:字典或CookieJar,Request中的auth : 元组支持HTTP认证功能
- files:字典类型,传输文件,作为post请求文件流数据,测试文件上传接口时使用
- timeout:设定超时时间,单位为秒
params、data、json区别:
在接口请求中,使用哪一个参数需要根据接口请求方法和编码格式而定,三者的大致区别如下:
- params:通常作为get请求中接收参数,
params=字典类型的数据
- json:通常作为post、put、delete等请求中接收参数,
json=字典格式的数据
(content_type
为application/json
时使用) - data:与json一样,作为请求中的接收参数,
data=字典格式的数据
(content_type
为键值对的编码格式时使用,比如application/x-www-form-urlencoded
)
实际案例
以测试开源项目Halo为例,此项目可以在GitHub上找到,安装到本地虚机进行测试的通过抓包获取接口后即可用来学习,首先需要登录,如下是Halo的登录接口测试
import requests # 导入requests库
class TestCom(object): # 定义一个名为TestCom的类,默认继承object基类
base_url = "http://192.166.66.24:8090" # 因为测试环境可能切换,所以可以定义个base_url
@classmethod # 使用类方法
def setup_class(cls): # 以为是基于pytest框架,所以使用pytest中的setup,实现操作前先登录
login_url = cls.base_url + "/api/admin/login" # 定义接口地址,由base_url与路径拼接
login_data = {"username": "admin", "password": "admin123", "authcode": None} # 登录信息
r_login = cls.request(method='post', url=login_url, json=login_data) # 发起post请求
cls.token = r_login.json()['data']['access_token'] # 获取响应中的token值
除登录外,其它接口请求头中需要带token值,所以,以类的继承方式进行token传递,将上面的登录请求封装到公共方法中,创建文件common.py
将登录请求放到此文件中,然后编写其它接口的测试,先创建文件test_art.py
,然后开始写接口用例
import requests # 导入requests
import pytest # 导入pytest
from com.common import TestCom # 导入common.py中的TestCom类
class TestArt(TestCom): # 定义一个名为TestArt的类,继承TestCom类
def test_artlist(self): # 这是查看文章列表的接口请求
artlist_url = self.base_url+"/api/admin/posts" # 使用公共方法中base_url与请求路径拼接
header = {"Admin-Authorization": self.token} # 使用基类中的token值
artlist_params = {"page":"1","size":"10"} # get请求中的参数,使用requests中的params参数
r_artlist = requests.request("get",url=artlist_url,headers=header,params=artlist_params)
print(r_artlist.json()) # 打印JSON格式的响应结果
assert r_artlist.json()["message"] == "OK" # 断言,判断响应的信息和状态码是否与预期结果一致
assert r_artlist.status_code == 200
def test_artwrite(self): # 这是写文章的接口请求
artwrite_url = self.base_url+"/api/admin/posts" # 使用公共方法中base_url与请求路径拼接
header = {"Admin-Authorization": self.token}
art_data={"title":"无题六首其三", # 请求参数,请求头使用TestArt类中定义的header
"originalContent":"直道相思了无益,未妨惆怅是清狂。",
"status":"DRAFT",
"keepRaw":True }
r_artwrite = requests.request('POST',artwrite_url,headers=header,json=art_data)
art_id = str(r_artwrite.json()['data']['id']) # 获取响应结果中文章的id
globals()["art_ids"] =art_id # 将art_id设为全局变量,供下面的接口调用
assert r_artwrite.json()["message"] == "OK" # 断言
assert r_artwrite.status_code == 200
def test_artrecycle(self): # 这是将文章放到回收站的接口请求
artrecycle_url = self.base_url+"/api/admin/posts/"+globals()["art_ids"]+"/status/RECYCLE"
header = {"Admin-Authorization": self.token}
r_artrecycle = requests.request('put',artrecycle_url,headers=header)
assert r_artrecycle.json()["message"] == "OK"
assert r_artrecycle.status_code == 200
def test_artdelete(self): # 这是将文章从回收站彻底删除的接口请求
artdelete_url = self.base_url + "/api/admin/posts"
artdelete_id = [globals()["art_ids"]] # 请求体需要列表格式,将文章id转换为列表格式后赋值给artdelete_id
header = {"Admin-Authorization": self.token}
r_artdelete = requests.request('delete', url=artdelete_url, headers=header, json=artdelete_id)
assert r_artdelete.json()["message"] == "OK"
assert r_artdelete.status_code == 200
添加日志功能
测试工作肯定是少不了工作记录的,使用日志记录接口执行情况,创建文件logger.py
,定义日志信息,如下:
import logging
import os
logger = logging.getLogger("dyd-测试日志") # 定义日志并设置名称然后赋值给logger
logger.setLevel(logging.DEBUG) # 设置日志为DEBUG级别
# 定义日志格式,输出格式为:当前时间 - 日志等级 - 函数名 - 日志信息
format = logging.Formatter("%(asctime)s - %(levelname)s - %(funcName)s - %(message)s")
yd = logging.StreamHandler() # 输出日志记录
yd.setFormatter(format) # 输出日志使用定义的format格式
logger.addHandler(yd) # 日志输出到控制台
# 定义日志存放目录
log = os.path.join(os.path.dirname(__file__),"../logs") # 获取当前路径,返回上一级进入logs目录
if not os.path.exists(log): # 如果logs目录不存在,就先创建logs目录
os.mkdir(log)
logfiles = os.path.join(log,"APItest.log")
re = logging.FileHandler(logfiles) # 日志记录到指定文件中
re.setFormatter(format) # 输出日志使用定义的format格式
logger.addHandler(re) # 日志输出到控制台
将日志运用到实际项目的接口测试中,需要在任何要保存日志的地方添加要记录的信息,如下示例:
import requests
from com.common import TestCom
from com.logger import logger # 导入定义的日志模块
class TestArt(TestCom):
def test_artlist(self): # 这是查看文章列表的接口请求
artlist_url = self.base_url + "/api/admin/posts"
header = {"Admin-Authorization": self.token}
r_artlist = requests.request("get", url=artlist_url, headers=header)
logger.debug(f"响应结果:{r_artlist.text}") # 添加响应结果日志
assert r_artlist.json()["message"] == "OK"
assert r_artlist.status_code == 200
def test_artwrite(self): # 这是写文章的接口请求
artwrite_url = self.base_url + "/api/admin/posts"
header = {"Admin-Authorization": self.token}
art_data = {"title": "无题六首其三", "originalContent": "直道相思了无益,未妨惆怅是清狂。", "keepRaw": True}
r_artwrite = requests.request('POST', artwrite_url, headers=header, json=art_data)
logger.info(f"请求头:{header},请求地址:{artwrite_url},响应状态码:{r_artwrite.status_code}")
assert r_artwrite.json()["message"] == "OK"
assert r_artwrite.status_code == 200
封装requests库
根据实际项目可以自己封装requests库,更便于自动化测试,首先将requests库封装到在common.py
文件中,并添加日志功能,如下:
import requests
from com.logger import logger # 导入定义的日志模块
class TestCom(object):
base_url = "http://192.166.66.24:8090"
@classmethod
def setup_class(cls):
login_url = cls.base_url + "/api/admin/login"
login_data = {"username": "admin", "password": "admin123", "authcode": None}
r_login = cls.request(method='post', url=login_url, json=login_data) # 使用下面封装的请求
cls.token = r_login.json()['data']['access_token']
@classmethod
def request(self, method: str, url, params=None, data=None, json=None, **kwargs): # 自定义发送请求
"""自定义发送请求
请求方法为字符串格式,params、data、json数据可以为空
method:请求方法
url:请求URL
params:get请求的参数
data:body中的数据
json:body中json格式的数据
kwargs:其它字典参数,允许传入不定长的参数
"""
method = method.upper() # 将请求方法转换成大写
if method == "GET": # 当请求方法为GET时,调用requests库中的get请求
res = requests.get(url, params=params, **kwargs)
# 输出以下日志信息,可以添加更多信息,比如请求头,响应状态等
logger.info(f"请求方法:{method},请求地址:{url},请求参数:{res.request.body},响应结果:{res.text}")
return res # 返回请求
elif method == "POST": # 当请求方法为POST时,调用requests库中的POST请求
res = requests.post(url, data=data, json=json, **kwargs)
logger.info(f"请求方法:{method},请求地址:{url},请求参数:{res.request.body},响应结果:{res.text}")
return res
elif method == "PUT": # 当请求方法为PUT时,调用requests库中的PUT请求
res = requests.put(url, data=data, json=json, **kwargs)
logger.info(f"请求方法:{method},请求地址:{url},请求参数:{res.request.body},响应结果:{res.text}")
return res
elif method == "DELETE":# 当请求方法为DELETE时,调用requests库中的DELETE请求
res = requests.delete(url, data=data, json=json, **kwargs)
logger.info(f"请求方法:{method},请求地址:{url},请求参数:{res.request.body},响应结果:{res.text}")
return res
else: # 如果不是以上4种请求方法,则提示"请求方法未定义,请检查!"
print("请求方法未定义,请检查!")
上面的登录接口请求使用了自己封装后的请求方法,其它接口测试同样也可以使用自己封装的方法,如下所示:
from com.common import TestCom
import pytest
class TestArt(TestCom):
def test_artlist(self): # 这是查看文章列表的接口请求
artlist_url = self.base_url+"/api/admin/posts"
header = {"Admin-Authorization": self.token}
artlist_params = {"page":"1","size":"10"}
r_artlist = self.request("get",url=artlist_url,headers=header,params=artlist_params)
assert r_artlist.json()["message"] == "OK"
assert r_artlist.status_code == 200
def test_artwrite(self): # 这是写文章的接口请求
artwrite_url = self.base_url+"/api/admin/posts"
header = {"Admin-Authorization": self.token}
art_data={"title":"无题六首其三","originalContent":"直道相思了无益,未妨惆怅是清狂。","keepRaw":True }
r_artwrite = self.request(method='POST', url=artwrite_url, headers=header, json=art_data)
art_id = str(r_artwrite.json()['data']['id'])
globals()["art_ids"] =art_id
assert r_artwrite.json()["message"] == "OK"
assert r_artwrite.status_code == 200
def test_artrecycle(self): # 这是将文章放到回收站的接口请求
artrecycle_id=globals()["art_ids"] # 将全局变量进行赋值,下面使用f方式进行字符串拼接
artrecycle_url = self.base_url+f"/api/admin/posts/{artrecycle_id}/status/RECYCLE"
header = {"Admin-Authorization": self.token}
r_artrecycle = self.request('put', url=artrecycle_url, headers=header)
assert r_artrecycle.json()["message"] == "OK"
assert r_artrecycle.status_code == 200
def test_artdelete(self): # 这是将文章从回收站彻底删除的接口请求
artdelete_url = self.base_url + "/api/admin/posts"
artdelete_id = [globals()["art_ids"]]
header = {"Admin-Authorization": self.token}
r_artdelete = self.request('delete', url=artdelete_url, headers=header, json=artdelete_id)
assert r_artdelete.json()["message"] == "OK"
assert r_artdelete.status_code == 200
if __name__ == '__main__':
pytest.main(["-sv","test_art.py"])
使用pytest装饰器实现参数化
使用参数化完成所有接口请求,不再需要测试一条用例就要定义一个方法,如下所示:
from com.common import TestCom
import pytest
class TestArt(TestCom):
# 定义传参,同时根据响应状态码和响应文本进行断言
art_data = [("get", "/api/admin/posts", None, 200, "OK"),
("get", "/api/admin/posts", {"page": "1", "size": "5"}, 200, "OK"),
("post", "/api/admin/posts", {"title": "", "Content": "内容", "status": "DRAFT", "keepRaw": True}, 400,"字段验证错误,请完善后重试!"),
("post", "/api/admin/posts",{"title": "test", "Content": "content", "status": "DRAFT", "keepRaw": True}, 400, "文章别名 test 已存在"),
("post", "/api/admin/posts", {"title": "文章标题", "Content": "文章内容", "status": "DRAFT", "keepRaw": True},200, "OK"),
("put", "/api/admin/posts/232/status/RECYCLE", None, 404, "Post was not found or has been deleted"),
("put", "/api/admin/posts/230/status/RECYCLE", None, 200, "OK"),
("delete", "/api/admin/posts", [22], 404, "content was not found or has been deleted"),
("delete", "/api/admin/posts", [485], 200, "OK")]
# 使用装饰器定义参数名
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg): # 参数同步到方法中
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=art_body)
assert r_art.json()["message"] == msg # 根据定义的参数进行传参和断言
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv","test_art.py"])
通过读取Json文件实现参数化
使用json文件进行参数化测试,将上文中的art_data参数改为json格式,如下所示
[["get", "/api/admin/posts", null, 200, "OK"],
["get", "/api/admin/posts", {"page": "0", "size": "1"}, 200, "OK"],
["post", "/api/admin/posts", {"title": "", "Content": "内容", "status": "DRAFT", "keepRaw": "True"}, 400,"字段验证错误,请完善后重试!"],
["post", "/api/admin/posts",{"title": "test", "Content": "content", "status": "DRAFT", "keepRaw": "True"}, 400, "文章别名 test 已存在"],
["post", "/api/admin/posts", {"title": "文章标题", "Content": "文章内容", "status": "DRAFT", "keepRaw": "True"},200, "OK"],
["put", "/api/admin/posts/232/status/RECYCLE", null, 404, "Post was not found or has been deleted"],
["put", "/api/admin/posts/230/status/RECYCLE", null, 200, "OK"],
["delete", "/api/admin/posts", [22], 404, "content was not found or has been deleted"],
["delete", "/api/admin/posts", [487], 200, "OK"]]
然后读取json文件中的数据,进行数据驱动
from com.common import TestCom
import pytest
import json
class TestArt(TestCom):
# 打开并读取json文件的中art_data数据
art_data = json.load(open("../data/art_data.json", "r", encoding="utf8"))
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=art_body)
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
通过读取Yaml文件实现参数化
yaml格式相对于json格式的数据更清晰明朗,自己书写需要先了解规则,实际使用哪种格式的数据,请根据实际项目需要和自身对两个数据格式的了解程度决定,这里就直接将上面的json数据转换为yaml格式使用啦,数据如下:
- - get
- /api/admin/posts
- null
- 200
- OK
- - get
- /api/admin/posts
- page: '0'
size: '1'
- 200
- OK
…省略了一部分…
然后读取yaml文件中的数据,然后执行用例数据,进而实现参数化测试
from com.common import TestCom
import pytest
import yaml
class TestArt(TestCom):
# 跟上文中打开json方式类似,如下所示
# art_data = yaml.load(open("../data/art_data.yaml","r",encoding="utf8"),Loader=yaml.FullLoader)
# 打开并读取yaml文件的中art_data数据,这次使用with方式打开,这里只是说明还有另一种方式
with open("../data/test_art.yaml", "r", encoding="utf8") as f:
art_data = yaml.load(f.read(), Loader=yaml.FullLoader)
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=art_body)
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
openpyxl库介绍
本次使用openpyxl库,openpyxl是支持读写Excel的python库,支持xlsx格式
的Excel文件,能够同时读取和修改Excel文档,安装命令pip install openpyxl
,下面简单介绍一下如何使用,更多信息请查看官方文档
from openpyxl import load_workbook # 导入库
wb = load_workbook("../data/test_data.xlsx") # 打开已存在的excel文件
sheet = wb["登录模块"] # 指定打开名为“登录模块”的工作表
sheet = wb[wb.sheetnames[0]] # 也可以使用下标的方式打开工作表
sheet["A1"] # 选择单元格
sheet.cell(1,1) # 或者使用坐标方式,先行后列
sheet["A1"].value # 获取单元格的值
row = sheet.max_row # 获取总行数
row = len(tuple(sheet.rows)) # 或者使用此方式获取总行数
col = sheet.max_column # 获取总列数
col = len(tuple(sheet.columns)) # 或者使用此方式获取总列数
for x in range(1,row+1): # 使用for循环打印各单元格中的值
for y in range(1,col+1): # 需要注意的是,不能从0开始,所以要+1,否则拿不到最后一行或一列的值
print(sheet.cell(row=x, column=y).value)
sheet["A1"] = 22 # 给单元格赋值,或者说是修改单元格的值
shell.cell(2,1).value = "用例标题" # 或者使用坐标的方式赋值
wb.save("../data/test_data.xlsx") # 保存文件,对于修改文件操作,必须有保存操作,否者修改不生效
另外需要注意的是对于xlsx格式的Excel文件要使用安装的office工具创建,网上说是office加密处理了,比如将a.txt直接改后缀a.xlsx的方式是不对的,也不能直接在PyCharm之类的工具创建xlsx格式的文件,在打开时会报文件扩展名无效之类的错误,总之,最简单的方法还是在电脑上通过右键创建Excel文件,然后放到所需位置,当然网上也有其它办法,这里就不细说啦!
通过读取Excel文件实现参数化
首先将测试用例写入Excel文件中,如下图所示
使用openpyxl读取Excel文件中的信息,完成参数化测试,如下所示:
from com.common import TestCom
import pytest
import json
from openpyxl import load_workbook
class TestArt(TestCom):
wb = load_workbook("../data/test_art.xlsx") # 打开Excel文件
sheet = wb[wb.sheetnames[0]] # 打开第一个工作表
total_rows = sheet.max_row # 获取总行数
art_data = [] # 新建一个空列表,将读取出来的每行数据存放到列表中
for x in range(2, total_rows + 1):
case_data = [] # 组装每行列表数据,形成一个列表集合
for y in range(3, 8): # 获取第3列到第7列的数据
case_data.append(sheet.cell(row=x, column=y).value) # 将每行单元格数据添加到case_data列表中
art_data.append(case_data) # 将每行数据添加到art_data列表中
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=json.loads(art_body))
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
封装文件解析方法
新建file_tools.py
文件,封装json文件、yaml文件和Excel文件的解析方法
import json
import yaml
from openpyxl import load_workbook
class FileTools():
def json_file(self,filename): # 打开json文件并读取数据
art_data = json.load(open(filename, "r", encoding="utf8"))
return art_data
def yaml_file(self,filename): # 打开yaml文件并读取数据
with open(filename, "r", encoding="utf8") as f:
art_data = yaml.load(f.read(), Loader=yaml.FullLoader)
return art_data
def excel_file(self,filename,sheetname): # 打开Excel文件并读取数据
wb = load_workbook(filename) # 打开指定Excel文件
sheet = wb[sheetname] # 打开指定工作表
total_rows = sheet.max_row # 获取总行数
art_data = [] # 新建一个空列表,将读取出来的每行数据存放到列表中
for x in range(2, total_rows + 1): # 读取每行数据
case_data = [] # 组装每行列表数据,形成一个列表集合
for y in range(3, 8): # 获取第3列到第7列的数据
case_data.append(sheet.cell(row=x, column=y).value) # 将每行单元格数据添加到case_data列表中
art_data.append(case_data) # 将每行数据添加到art_data列表中
return art_data
以读取Excel文件为例,调用封装后的方法即可
from com.common import TestCom
import pytest
import json
from com.file_tools import FileTools # 导入封装FileTools类
class TestArt(TestCom):
# 调用封装的方法
art_data = FileTools().excel_file("../data/art_data.xlsx", "Sheet1")
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=json.loads(art_body))
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
以上就完成啦!使用Excel文件实现参数化相对较复杂一点,而且上面的示例也没有将测试结果写入到Excel文件中,只是读取了Excel中的用例,所以既然使用Excel文件就把结果记录到文件中吧,考虑到Excel中可能记录了各种场景的测试用例,若只想执行某几条,又不想增删Excel中其它数据,可以加个判断,只执行做了标记的用例,方法如下:
首先在Excel文件中增加用例ID、是否执行、和测试结果字段,先根据是否执行判断哪些用例需要执行,然后将结果写入对应的测试结果中,Excel如下图所示
重新封装Excel解析方法
封装读取Excel文件和写入Excel文件的方法,代码如下:
class FileTools():
# 封装读取Excel文件
def read_excel(self,filename,sheetname):
wb = load_workbook(filename) # 打开指定的Excel文件
sheet = wb[sheetname] # 打开指定的工作表
total_rows = sheet.max_row # 获取总行数
art_data = [] # 新建一个空列表,将读取出来的每行数据存放到列表中
for x in range(2, total_rows + 1): # 循环读取每行数据
case_data = [] # 组装每行列表数据,形成一个列表集合
for y in range(3, 10): # 获取第3列到第9列的数据
case_data.append(sheet.cell(row=x, column=y).value) # 将每行单元格数据添加到case_data列表中
art_data.append(case_data) # 将每行数据添加到art_data列表中
return art_data # 返回art_data
# 封装写入Excel文件
def write_excel(self,filename,id,result):
wb = load_workbook(filename) # 打开指定的Excel文件
sheet = wb[wb.sheetnames[0]] # 打开第一个工作表
sheet.cell(id + 1, 10).value = result # 将测试结果写入Excel文件中
wb.save(filename) # 保存修改后的Excel文件
调用封装的方法进行测试,注意:因为有写入操作,执行时Excel文件必须处于关闭状态,否则报权限被拒
from com.common import TestCom
import pytest
import json
from com.file_tools import FileTools
class TestArt(TestCom):
# 读取Excel文件中的数据
art_data = FileTools().read_excel("../data/art_data.xlsx", "Sheet1")
@pytest.mark.parametrize("case_id,res_method,url_path,art_body,status_code,msg,is_run", art_data)
def test_art(self, case_id, res_method, url_path, art_body, status_code, msg, is_run):
if is_run == "是": # 只执行标记“是”的用例
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=json.loads(art_body))
if r_art.status_code == status_code and r_art.json()["message"] == msg: # 使用if…else做断言
real_result = "Pass"
else:
real_result = "Fail"
# 保存填写测试结果后的Excel文件
FileTools().write_excel("../data/art_data.xlsx", case_id, real_result)
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
测试报告
Pytest-html报告
可以使用pytest-html生成报告,先执行命令pip install pytest_html
安装,运行测试用例时加上--html = report.html
参数即可
from com.common import TestCom
import pytest
from com.file_tools import FileTools
class TestArt(TestCom):
art_data = FileTools().json_file("../data/art_data.json")
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg,):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=art_body)
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py", "--html=../report/art_report.html"]) # 执行用例并生成报告到report目录下
报告如下所示
Allure报告
当然也可以使用更强大的Allure报告,关于Allure安装使用请查看此文章,这里只做简单演示
from com.common import TestCom
import pytest, os
from com.file_tools import FileTools
class TestArt(TestCom):
art_data = FileTools().yaml_file("../data/art_data.yaml")
@pytest.mark.parametrize("res_method,url_path,art_body,status_code,msg", art_data)
def test_art(self, res_method, url_path, art_body, status_code, msg, ):
res_url = self.base_url + url_path
header = {"Admin-Authorization": self.token}
r_art = self.request(method=res_method, url=res_url, headers=header, json=art_body)
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__': # 执行用例并生成Allure报告
pytest.main(["-sv", "test_art.py","--alluredir=../report/allure-results", "--clean-alluredir"])
os.system("allure generate ../report/allure-results -o ../report/allure-report")
补充:文件上传
忘记写上传文件的接口了,这里补充一下吧,其实跟读取文件类似,首先在封装的request中添加files=None
,或者直接使用requests库,如下所示,使用的是自己封装的request函数,注意:上传文件需要使用读取二进制的方式,用rb
表示,注意文件类型,不同文件的Content-Type
肯定也是不同的
from com.common import TestCom
import pytest
from com.file_tools import FileTools
class TestArt(TestCom):
def test_art(self):
res_url = self.base_url + "/api/admin/attachments/upload"
file_data = {"file":("这里指上传后的图片名称.jpg",open("../data/picture.jpg", "rb"),"image/jpeg")}
header = {"Admin-Authorization": self.token}
r_art = self.request(method="post", url=res_url, headers=header, files=file_data)
assert r_art.json()["message"] == "OK"
assert r_art.status_code == 200
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
同样可以封装上传文件的解析方法后再调用,在file_tools.py
中添加以下信息
def upload_file(self,filename,filepath,filetype):
file_data = {"file":(filename,open(filepath, "rb"),filetype)}
return file_data
封装后就可以调用解析方法啦,同时可以像测试上面介绍的其它接口一样,也可以实现参数化,如下所示
from com.common import TestCom
import pytest
from com.file_tools import FileTools
class TestArt(TestCom):
# 参数化数据
file_data = [("上传jpg格式的图片.jpg", "../data/picture.jpg", "image/jpeg", 200, "OK"),
("上传png格式的图片.png", "../data/icon.png", "image/png", 200, "OK"),
("上传txt格式的文档.txt", "../data/新建文本文档.txt", "text/plain", 200, "OK"),
("上传json格式的文件.json", "../data/art_data.json", "application/json", 200, "OK"),
("上传yaml格式的文件.yaml", "../data/art_data.yaml", "application/octet-stream", 200, "OK"),
("上传cer格式的证书.cer", "../data/Root.cer", "application/x-x509-ca-cert", 200, "OK"),
("上传exe格式的程序.exe", "../data/geek.exe", "application/x-msdownload", 200, "OK"),
("上传xlsx格式的文档.xlsx", "../data/art_data.xlsx",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", 200, "OK")]
@pytest.mark.parametrize("filename,filepath,filetype,status_code,msg", file_data)
def test_art(self, filename, filepath, filetype, status_code, msg):
res_url = self.base_url + "/api/admin/attachments/upload"
file_data = FileTools().upload_file(filename, filepath, filetype) # 调用封装的文件解析函数
header = {"Admin-Authorization": self.token}
r_art = self.request(method="post", url=res_url, headers=header, files=file_data)
assert r_art.json()["message"] == msg
assert r_art.status_code == status_code
if __name__ == '__main__':
pytest.main(["-sv", "test_art.py"])
至此,Pytest+Requests+Logging+报告的自动化框架基本就完成了,更多功能及其实现方法还请自行探索吧!