ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Pytest+Requests+Logging接口自动化测试框架搭建

2022-05-21 00:33:00  阅读:40  来源: 互联网

标签:status art url self json Pytest Logging Requests data


目录
关于Pytest和Allure如何使用请查看此文章,这里不再介绍

Requests库介绍

requests库是Python第三方库,用于模拟浏览器向服务器发出请求,比如:常用的GETPOSTPUTDELETE等请求,发起对应请求可以使用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_typeapplication/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")

至此,Pytest+Requests+Logging+报告的自动化框架基本就完成了,更多功能还请自行扩展吧,另,请不要评论或私信要源码了,文章中就是整理好的源码,思路和方法才是最重要的,搞明白并自己动手敲一遍肯定会有更大的进步

标签:status,art,url,self,json,Pytest,Logging,Requests,data
来源: https://www.cnblogs.com/dyd168/p/16294028.html

专注分享技术,共同学习,共同进步。侵权联系[admin#icode9.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有