ICode9

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

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

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

标签: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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

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

ICode9版权所有