Pytest中实现自动生成测试用例脚本代码!

前言

在Python的测试框架中,我们通常会针对某个系统进行测试用例的维护,在对庞大系统进行用例维护时,往往会发现很多测试用例是差不多的,甚至大多数代码是一样的。

故为了提高我们测试用例维护的效率,在本文中,我们将探讨如何自己设计脚本,自动生成测试用例脚本。

自动发现测试原理

pytest的一个重要特性就是它可以自动发现并运行测试。所以我们只需要按照一定的命名规则来命名测试文件和测试函数,然后pytest就可以自动找到并运行这些测试。

默认情况下,pytest会运行所有名称以test_开头或者以_test结尾的.py文件中的测试。在这些文件中,pytest会运行所有名称以test_开头的函数或者方法。

例如,以下代码定义了一个测试函数:

def test_addition():assert 1 + 1 == 2

你可以把这个函数保存在一个名为test_example.py的文件中,然后在命令行中运行pytest命令,pytest就会自动找到并运行这个测试。

接口测试数据维护

为了达到目的,所以我们约定以特定规则进行接口测试数据的维护,我们选择json类型文件进行维护,文件目录结构如下:

需要注意的是,考虑到维护的便利性,我们推荐根据模块进行分层管理接口数据文件。

读取接口测试文件信息方法

有了接口数据文件,那么我们就可以设计一个方法,根据传入的接口模块、接口名称,进行相应的接口文件地址拼接,从而去读取json文件内容,代码如下:

class GetTestData:"""获取测试数据封装模块"""def __init__(self, logger):self.test_data_path = FilePathSetting.test_data_path  # 获取测试数据配置路径self.logger = loggerdef get_api_data(self, api_module, api_name):"""根据接口模块和接口名称 获取接口配置信息:param api_module:接口模块:param api_name:接口名称:return:"""# 拼接需要读取接口模板信息的*.json文件路径地址api_data_path = os.path.join(os.path.join(self.test_data_path, api_module), api_name)self.logger.info("{init_api_data} 接口模板文件地址:%s" % api_data_path)# 读取接口模板路径下的*.json信息with open(file=api_data_path, mode='r', encoding='utf-8') as f:api_data = json.load(f)self.logger.info("{init_api_data} 接口模板信息:%s" % json.dumps(api_data, ensure_ascii=False))return api_datadef get_case_data(self, pytest_current_test):"""根据传入的test_*.py路径 获取case配置信息:param pytest_current_test::return:"""# self.logger.info('~~~~~~~~~~~~ {init_api_data} 初始化当前接口请求模板数据 ~~~~~~~~~~~~')self.logger.info('{init_api_data} 当前test_case位置信息:%s' % pytest_current_test)# 根据传入的test_*.py路径 获取接口所属模块文件名称file_path = pytest_current_test.split("::Test")[0]self.logger.info('{init_api_data} 当前接口test_case文件地址:%s' % file_path)# 根据传入的test_*.py路径 获取接口所属模块文件名称api_module = os.path.split(os.path.split(file_path)[0])[1]self.logger.info("{init_api_data} 接口所属模块:%s" % api_module)# 根据传入的test_*.py路径 获取接口模板(*.json)名称# # api_name = os.path.split(file_path)[1].replace('.py', '.json').replace('test_', '')api_name = os.path.split(file_path)[1][5:].replace('.py', '.json')  # 1、截取第6个字符到结尾;2、将.py后缀替换为.jsonself.logger.info("{init_api_data} 接口模板文件名称:%s" % api_name)return self.get_api_data(api_module, api_name)
现在我也找了很多测试的朋友,做了一个分享技术的交流群,共享了很多我们收集的技术文档和视频教程。
如果你不想再体验自学时找不到资源,没人解答问题,坚持几天便放弃的感受
可以加入我们一起交流。而且还有很多在自动化,性能,安全,测试开发等等方面有一定建树的技术大牛
分享他们的经验,还会分享很多直播讲座和技术沙龙
可以免费学习!划重点!开源的!!!
qq群号:691998057【暗号:csdn999】

基础的测试用例模板

因为我们基础的测试用例是通用的,如以下代码实例:

import allure
import pytestfrom utils.assertion.assert_controller import AssertController
from utils.requests_tools.requests_client import RequestClientapi_module = '接口模块***'
api_name = '接口名称***'@allure.epic(api_module)
@allure.feature(api_module + '' + api_name)
class TestCaseGroup:@allure.title("接口健康检查")@allure.description("接口返回信息基础验证")@pytest.mark.smokedef test_smoke(self, init_api_data):# 获取环境配置信息&接口测试数据env_data, case_data = init_api_data# 获取接口返回信息res = RequestClient(case_data).get_api_response()# 进行断言验证AssertController(case_data.assert_data).assert_equality(res.response_data)

故我们就可以设计一个用例模板的生成器:

import datetime
from typing import Textdef write_case(file_path: Text, page: Text) -> None:""" 写入用例数据 """with open(file_path, 'w', encoding="utf-8") as file:file.write(page)def write_init_file(*, file_path: Text) -> None:"""初始化文件写入:param file_path: __init__文件地址:return:"""now = datetime.datetime.now().strftime('%Y/%m/%d %H:%M')page = f'''#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author    : gmluo1988
# @Software  : PyCharm
# @File      : __init__.py.py
# @Time      : {now}
'''write_case(file_path=file_path, page=page)def write_test_case_file(*,api_module: Text,api_name: Text,filename: Text,case_path: Text) -> None:"""标准测试用例文件写入:param api_module: 接口所属模块:param api_name: 接口名称名称:param filename: 用例文件名称:param case_path: 用例文件地址"""now = datetime.datetime.now().strftime('%Y/%m/%d %H:%M')page = f'''#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Author    : gmluo1988
# @Software  : PyCharm
# @File      : {filename}
# @Time      : {now}import allure
import pytestfrom utils.assertion.assert_controller import AssertController
from utils.requests_tools.requests_client import RequestClient@allure.epic("{api_module}")
@allure.feature("{api_module} {api_name}")
class TestCaseGroup:@allure.title("接口健康检查")@allure.description("接口返回信息基础验证")@pytest.mark.smokedef test_smoke(self, init_api_data):# 获取环境配置信息&接口测试数据env_data, case_data = init_api_data# 获取接口返回信息res = RequestClient(case_data).get_api_response()# 进行断言验证AssertController(case_data.assert_data).assert_equality(res.response_data)
'''write_case(file_path=case_path, page=page)

有了测试用例生成器,那么我们就可以根据接口模块,接口名称,以及接口相关信息生成通用的接口用例文件了。

自动生成测试用例

接下来要做事情就是读取test_data目录下所有模块下的接口文件,遍历文件列表进行测试用例的字段生成。

要注意的是:

1、该封装方法可以根据需求设置需要排除的文件列表

2、test_case目录下已经存在的用例会跳过生成

3、该封装方法生成的是一个基本通用的用例,可在生成的用例基础上进行改造

import osfrom common.file_path_setting import FilePathSetting
from utils.case_automatic.test_case_template import write_test_case_file, write_init_file
from utils.get_test_data_tools.get_case_data import GetCaseDataclass CaseAutoGenerate:"""测试用例自动生成工具"""def __init__(self):"""设置需要排除的文件列表,一般是Cloud登录以及子系统跳转登录等前置条件接口文档"""self.excluded_file_list = [("users", "login.json"),("users", "change_password.json")]def get_test_data_files(self) -> list:"""获取所有测试数据文件路径信息列表每条测试数据由一个tuple组成,包含3个值(filename:文件名;parent_dir:父级目录名称;file_path:文件地址)需要排除不需要自动生成测试用例的文件:return:测试数据文件路径信息列表"""file_info_list = []  # 所有测试数据文件路径信息列表# 获取测试数据目录所有文件下的子文件名称for root, dirs, filenames in os.walk(FilePathSetting.test_data_path):for filename in filenames:parent_dir = os.path.basename(root)  # 测试数据文件父级目录名称file_path = os.path.join(root, filename)  # 测试数据文件地址# 需要排除不需要自动生成测试用例的文件if (parent_dir, filename) not in self.excluded_file_list:# 每条测试数据由一个tuple组成,包含3个值(filename:文件名;parent_dir:父级目录名称;file_path:文件地址)file_info_list.append((filename, parent_dir, file_path))# 返回所有测试数据文件路径信息列表return file_info_list@staticmethoddef get_test_case_files() -> tuple:"""获取所有测试用例父级目录列表,测试用例文件地址列表需要将'__init__.py', 'conftest.py'文件进行过滤:return:"""case_dir_list = []  # 用例父级目录列表case_path_list = []  # 测试用例文件地址列表# 获取测试用例目录下所有文件下的子文件名称for root, dirs, filenames in os.walk(FilePathSetting.test_case_path):for filename in filenames:parent_dir = os.path.basename(root)file_path = os.path.join(root, filename)# 需要将'__init__.py', 'conftest.py'文件进行过滤if filename not in ['__init__.py', 'conftest.py']:case_path_list.append(file_path)# 如果父级目录还未收录,则进行父级目录列表更新,并将'__pycache__'排除if parent_dir not in case_dir_list and parent_dir not in ['__pycache__']:case_dir_list.append(parent_dir)# 分别返回测试用例父级目录列表,测试用例文件地址列表return case_dir_list, case_path_list@staticmethoddef mk_dir(test_data_file_info: tuple, existing_case_dir_list: list) -> None:"""创建测试用例父级目录如果测试数据文件的父级目录在测试用例目录中是不存在,则在test_case目录下创建相同名称的package文件以及'__init__.py'文件:param test_data_file_info: 测试数据文件信息,包含3个值(filename:文件名;parent_dir:父级目录名称;file_path:文件地址):param existing_case_dir_list: 已有的所有测试用例父级目录列表:return:"""test_data_dir = test_data_file_info[1]  # 测试数据文件,父级目录名称# 如果测试数据文件的父级目录在测试用例目录中是不存在,则在test_case目录下创建相同名称的package文件以及'__init__.py'文件if test_data_dir not in existing_case_dir_list:print("test_case下缺少:{} 文件目录,进行文件新增操作...".format(test_data_dir))"""创建相同名称的package文件"""dir_path = os.path.join(FilePathSetting.test_case_path, test_data_dir)os.makedirs(dir_path, exist_ok=True)"""新增'__init__.py'文件"""init_file_path = os.path.join(dir_path, '__init__.py')# open(init_file_path, 'w').close()write_init_file(file_path=init_file_path)@staticmethoddef is_case_exist(test_data_file_info: tuple, existing_case_path_list: list) -> tuple:"""判断测试用例是否存在如果不存在,则返回需要生成测试用例的文件名和文件地址信息如果已经存在对应的测试用例,则返回None:param test_data_file_info::param existing_case_path_list::return:target_case_filename, target_case_file_path"""test_data_filename = test_data_file_info[0]  # 测试数据文件名称test_data_dir_name = test_data_file_info[1]  # 测试数据文件所属父级目录名称# 拼接目标测试用例所属父级目录地址target_case_dir_path = os.path.join(FilePathSetting.test_case_path, test_data_dir_name)# 组装目标测试用例文件名称:测试用例以“test_”开头,将文件格式“.json”替换成“.py”target_case_filename = 'test_' + test_data_filename.replace('.json', '.py')# 拼接目标测试用例文件地址target_case_file_path = os.path.join(target_case_dir_path, target_case_filename)# if target_case_file_path in existing_case_path_list:#     print(target_case_file_path)if target_case_file_path not in existing_case_path_list:# 返回需要生成测试用例的文件名和文件地址信息return target_case_filename, target_case_file_pathelse:return None, Nonedef case_automatic(self) -> None:"""测试用例自动生成入口:return:""""""获取所有测试数据文件路径信息列表"""test_data_file_info_list = self.get_test_data_files()"""获取所有测试用例父级目录列表,测试用例文件地址列表"""existing_case_dir_list, existing_case_path_list = self.get_test_case_files()"""遍历每个测试数据文件,如果对应的测试用例不存在,则进行测试用例自动生成操作"""for test_data_file_info in test_data_file_info_list:"""创建测试用例父级目录"""self.mk_dir(test_data_file_info, existing_case_dir_list)"""返回需要生成测试用例的文件名和文件地址信息"""target_case_filename, target_case_file_path = self.is_case_exist(test_data_file_info,existing_case_path_list)"""创建对应的测试用例文件"""if target_case_filename and target_case_file_path:print("测试用例文件:{} 不存在,进行测试文件新增...".format(target_case_file_path))test_data_file_path = test_data_file_info[2]case_data = GetCaseData().get_api_data_by_path(test_data_file_path)api_module = case_data.api_moduleapi_name = case_data.api_namewrite_test_case_file(api_module=api_module,api_name=api_name,filename=target_case_filename,case_path=target_case_file_path)if __name__ == '__main__':print("Generating test case...")case_auto_generate = CaseAutoGenerate()case_auto_generate.case_automatic()

总结

Python中通过自己设计脚本,从而能够自动生成测试用例脚本,提高我们测试用例维护的效率,当然不同的系统可能有自己特别的方式,在此是为大家作个参考。

下面是配套资料,对于做【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴我走过了最艰难的路程,希望也能帮助到你!

最后: 可以在公众号:自动化测试老司机 ! 免费领取一份216页软件测试工程师面试宝典文档资料。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/725283.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Redis缓存知识总结

图解缓存 what&how problems&strategy 缓存更新 缓存穿透 缓存雪崩 缓存击穿 (图源自黑马教育)

3.5日常学习

matlab处理数据 自己写了关于detect_data的函数,让它帮我改了,哈哈哈 %改正前function data_chuli(path1,savepath)[num]xlsread(path1,1,B18:F23);a num;ba;cb(:);xlswrite(savepath,c) end%改正后function data_chuli(path1, savepath)num xlsread…

100 spring-security 中 /oauth/token 发送请求不携带参数 报错 “401 Unauthorized“

前言 最近存在这样的一个问题, 大致的复现方式是 访问 /oauth/token 接口, 然后不携带任何参数, 结果 服务器抛出了一个 "401 Unauthorized" 针对这个 401, 这里 梳理一下这个流程, 也会衍生出一些其他的问题 测试用例 客户端这边大致的情况是 构造参数, 然后发…

HashMap 源码解读

文章目录 一、什么是HashMap HashMap 是一种快速的查找并且插入、删除性能都良好的一种 K/V键值对的数据结构,key唯一,value允许重复它基于哈希表的 Map 接口实现,是常用的 Java 集合之一,是非线程安全的。 二、HashMap的数据结…

rtt的io设备框架面向对象学习-触摸设备

目录 1.触摸设备基类2.触摸设备基类的子类3.初始化/构造流程3.1设备驱动层3.2 设备驱动框架层3.3 io设备管理层 4.总结5.使用5.1实例 1.触摸设备基类 此层处于设备驱动框架层。此层的类是抽象类。 在/ components / drivers / include / drivers /touch.h定义了如下touch设备…

C语言----冒泡排序进阶

冒泡排序大家应该到写过吧。但大家可能知道到的冒泡排序有两种方法。而我呢,最近学习到了另外一种方法,现在知道三种方法了。所以想与大家分享一下。但是缺点是第三种是第二种的自实现版。第一种就是我们平常写的普通冒泡排序。第二种就是qsort。第三种就…

Matlab梁单元有限元编程 | 铁木辛柯梁 | 欧拉梁 | Matlab源码 | 理论文本

专栏导读 作者简介:工学博士,高级工程师,专注于工业软件算法研究本文已收录于专栏:《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现,并提供所有案例完整源码;2.单元…

如何恢复edge的自动翻译功能

介绍:对于英文不好的小伙伴,把英语翻译成中文是有帮助的,而edge可以直接对英文页面翻译这一功能更是受人喜爱,但是,最近发现这一项功能消失了。 原始界面: 下面展示如何恢复该功能。 1.打开edge&#xff…

day06-网路编程

#include <myhead.h>int do_add(sqlite3 *ppDb) {int numb;char name[20];int age;int salary;printf("请输入要插入的信息:");scanf("%d %s %d %d", &numb, name, &age, &salary);char sql[128] "";sprintf(sql, "INSE…

Flutter(四):SingleChildScrollView、GridView

SingleChildScrollView、GridView 遇到的问题 以下代码会报错: class GridViewPage extends StatefulWidget {const GridViewPage({super.key});overrideState<GridViewPage> createState() > _GridViewPage(); }class _GridViewPage extends State<GridViewPage&g…

vscode 通义灵码 插件自动写代码

安装插件 通义灵码安装教程-阿里云 点击立即安装 我是已经安装成功了&#xff0c;所以如下图&#xff0c;没安装的会显示安装&#xff0c;点击安装即可 安装成功之后 侧边栏会出现图标 登录 使用 在编辑框中输入 问题 &#xff0c;会自动生成代码和对应的说明

Cloud+Consul

Cloud整合Zookeeper代替Eureka-CSDN博客 Consul简介 Consul是一套开源的分布式服务发现和配置管理系统 What is Consul? | Consul | HashiCorp DeveloperConsul is a service networking solution that delivers service discovery, service mesh, and network security ca…

Redis中的RDB和AOF持久化机制(一)

Redis持久化 RDB快照(snapshot). 在默认情况下&#xff0c;Redis将内存数据库快照保存在名字为dump.rdb的二进制文件中.Redis可以进行设置,让它在"N秒内数据集至少有M个改动"这一条件被满足时&#xff0c;自动保存一次数据集。比如说&#xff0c;以下设置会让Redis…

TypeScript常见面试题第一节

题目一&#xff1a;是否了解TypeScript&#xff1f;TypeScript比JavaScript 有哪些优势&#xff1f; 一、讲解视频 CSDN视频&#xff1a; TS面试题一&#xff1a;介绍TS及TS的优势&#xff1f; B站视频&#xff1a; TS面试题一&#xff1a;介绍TS及TS的优势&#xff1f; 二、…

关于Spark中OptimizeShuffleWithLocalRead 中自己的一些理解

背景 本文基于 Spark 3.5 关于ShuffleLocalRead的作用简单的来说&#xff0c;就是会按照一定的规则&#xff0c;从一个 map Task 中连续读取多个 reduce数据 的任务&#xff0c;&#xff08;正常的情况下是读取所有map Task中特定的一个reduce数据任务&#xff09;&#xff0c…

Effective C++ 学习笔记 条款13 以对象管理资源

假设我们使用一个用来塑模投资行为&#xff08;如股票、债券等等&#xff09;的程序库&#xff0c;其中各式各样的投资类型继承自一个root class Investment&#xff1a; class Investment { /* ... */ }; // “投资类型”继承体系中的root class进一步假设&#xff0c;这个…

机器视觉 /从bottle.hdev示例程序开启HalconHDevelop征程

文章目录 概述示例程序bottle.hdev源码Step 0: PreparationsStep 1: Segmentation - 读取并显示图片Step 1: Segmentation - 创建并设置OCR模型Step 1: Segmentation - 文本分割与识别计算结果显示内存释放 导出为C代码导出为C代码配置 VS Halcon 环境VS程序执行结果HTuple hv…

LeetCode刷题---填充每个节点的下一个右侧节点指针

官方题解:LeetCode官方题解 解题思想: 因为是一棵满二叉树&#xff0c;所以除了叶子节点外的其他节点都有两个子节点。 可以根据每一层来依次遍历 从根节点开始&#xff0c;根节点的左子节点的next节点就指向根节点的右子节点 因为根节点的next节点为NULL&#xff0c;开始从根…

centOS7操作系统安装说明

一、安装前准备 在安装CentOS 7之前&#xff0c;确保你已经下载了CentOS 7的ISO镜像文件。你可以从CentOS官网下载&#xff1a;The CentOS Project 1. 安装环境准备 确保你的计算机满足CentOS 7的最低系统要求。CentOS 7支持的最低系统要求如下&#xff1a; x86-64或x86架构…

DR模式下LVS负载均衡聚集部署实验

目录 1、实验准备 2、配置负载调度器&#xff08;ens33&#xff1a;192.168.80.9 VIP:192.168.80.188&#xff09; 2.1 配置虚拟ip地址&#xff08;VIP&#xff1a;192.168.80.188&#xff09; 2.2 调整proc响应参数 2.3 设置负载分配策略 3、部署共享存储&#xff08;NF…