【接口自动化测试实战】python接口自动化(自建库)
一、自动化项目介绍
1.涉及技术栈
python
requests
pytest
allure
2.实现的功能概述
支持requests各种请求
支持用例之间依赖关系
支持切换环境
支持按脚本的形式编写测试用例
支持生成html报告
二、框架及项目结构
项目目录
api_object:接口参数及扩展参数 config:配置文件,环境、账号、路径设置等 data:测试数据 files:测试文件 log:日志文件目录 report:测试报告目录 test_case:测试用例、测试集相关目录 utils:工具类,基础数据创建及获取等操作。
自建库
- 自建库(base-api)
- base_case
- 通用执行用例类
- 判断token状态及获取token
- 断言封装
- base_requests
- send_request(请求参数处理)
- 用例参数解析
- allure报告展示字段设置
- send_api(请求封装)
- base_case
- 自建库(tools)
- 测试数据构建工具
- DataProcess(数据处理)
- 提取参数处理
- 文件操作
无自建库可参考目录
api: 主程序目录 comm:公共函数,包括:接口请求基类、请求及相应数据操作基类等 intf_handle:接口操作层,包含:接口初始化、断言等 business:业务实现部分 utils:工具类,包括:读取文件、发送邮件、excel操作、数据库操作、日期时间格式化等 config:配置文件目录,包含yaml配置文件、以及路径配置 data:测试数据目录,用于存放测试数据 temp:临时文件目录,用于存放临时文件 result:结果目录 report:测试报告目录,用于存放生成的html报告 details:测试结果详情目录,用于存放生成的测试用例执行结果excel文件 log:日志文件目录 test:测试用例、测试集相关目录,启动test_suite执行用例文件存放在此 test_case:测试用例存放路径 test_suite:测试模块集,按模块组装用例
三、核心方法设计
接口请求基类
# -*- coding:utf-8 -*- import datetime import urllib import requests from tools import allure_step, allure_title, logger, allure_step_no, updateDict, get_r, form_data_type_judge from tools.data_process import DataProcess from tools.read_file import ReadFile class BaseRequest(object): session = None @classmethod def get_session(cls): """ 单例模式保证测试过程中使用的都是一个session对象 :return: """ if cls.session is None: cls.session = requests.Session() return cls.session @classmethod def send_request(cls, case: dict, reverse=True): """ 请求参数处理 :param case: 读取出来的每一行用例内容,可进行解包 :param env: 环境名称 默认使用config.yaml server下的 test 后面的基准地址 return: 响应结果, 预期结果 """ title = case.get('title') header = case.get('header') path = case.get('path') method = case.get('method') file_obj = case.get('files') params = case.get('params') data = case.get('data') jsonData = case.get('json') extract = case.get('extract') expect = case.get('expect') step = case.get('step') # 关联功能用例 relation = case.get('relation') # 自定义断言 expect_custom = case.get('expect_custom') logger.debug( f"\n用例进行处理前数据: \n用例标题:{title} \n 请求头:{header} \n 接口路径:{path} \n params:{params} \n data:{data} \n json:{jsonData} \n file:{file_obj} \n 提取参数:{extract} \n 预期结果:{expect} \n") # 如果不传title,就不设置 if title is not None: # allure报告 用例标题 allure_title(title) if step is not None: # 报告右侧步骤中增加步骤 allure_step_no(f'步骤: {step}') # 如果传method,默认为GET请求 if method is None: method = 'GET' # 处理url、header、data、files、的前置方法 url = DataProcess.handle_path(path) params = DataProcess.handle_data(params) data = DataProcess.handle_data(data) jsonData = DataProcess.handle_data(jsonData) # 每个请求增加_r参数 params = updateDict(params, "_r", get_r()) # 判断是否有resign,有的话就添加在params中 resign = ReadFile.read_config('$.resign') if resign != False: params = updateDict(params, "resign", resign) # 判断是否有bazi参数,有就增加在params,这个参数广告的需要,响应解密 bazi = ReadFile.read_config('$.bazi') if bazi != False: params = updateDict(params, "bazi", bazi) # 有请求数据再在报告中展示 requestsData = {} if params: requestsData.update({'params': params}) if data: # allure报告数据需要 requestsData.update({'data': data}) # 再判断data中有没有传"data_type": "multipart/form-data",如果有就处理为二进制方式提交数据(上传文件方式) data_handle = form_data_type_judge(data) if isinstance(data_handle, tuple): # 如果是元组,说明是这种类型,第一个数据是data转为二进制的数据,第二个是content-type的类型 data = data_handle[0] content_type = data_handle[1] if header is None: header = {} header.update({"Content-Type": content_type}) else: data = data_handle # 请求头处理 header = DataProcess.handle_header(header) if jsonData: requestsData.update({'json': jsonData}) allure_step('请求数据', requestsData) allure_step_no(f'请求时间: {datetime.datetime.now()}') file = DataProcess.handler_files(file_obj) # 发送请求 response = cls.send_api(url, method, header, params, data, jsonData, file) # 处理请求前extract在报告中展示 if extract is not None: allure_step("请求前extract", extract) # 提取参数 report_extract = DataProcess.handle_extra(extract, response) # 设置报告替换的extract if report_extract is not None or report_extract != {} or report_extract != "None": logger.info("请求后的extract" + str(report_extract)) allure_step("请求后extract", report_extract) logger.info("当前可用参数池" + str(DataProcess.extra_pool)) allure_step("当前可用参数池", DataProcess.extra_pool) # 如果没有填预期结果,默认断言响应中的success=True if expect is None and expect_custom is None: if reverse: expect = {"$.success": True} else: expect = {"$.success": False} if expect is not None: allure_step("请求前expect", expect) if expect_custom is not None: allure_step("请求前expect_custom", expect_custom) # 存储关联的用例id和维护人员 if relation is not None: relationId = relation.get("id") if relationId is not None: if relation not in DataProcess.extra_pool.get("relations"): DataProcess.extra_pool.get("relations").append(relation) return response, expect, expect_custom @classmethod def send_api(cls, url, method, header=None, params=None, data=None, jsonData=None, file=None, allure=True) -> dict: """ 封装请求 :param url: url :param method: get、post... :param header: 请求头 :param params: 查询参数类型,明文传输,一般在url?参数名=参数值 :param data: 一般用于form表单类型参数 :param jsonData: json类型参数 :param file: 文件参数 :return: 响应结果 """ session = cls.get_session() res = session.request(method, url, params=params, data=data, json=jsonData, files=file, headers=header) try: response = res.json() except Exception: # 这里return 二进制内容,文件下载需要接收 response = res.content if allure: allure_step_no(f'响应耗时(s): {res.elapsed.total_seconds()}') if isinstance(response, bytes): if "html" in str(response): response_result = res.text else: response_result = "响应结果为二进制文件" else: response_result = response if allure: allure_step("响应结果", response_result) logger.info( f'\n最终请求地址:{urllib.parse.unquote(res.url)}\n 请求方法:{method}\n 请求头:{res.request.headers}\n params:{params}\n data:{data}\n json:{jsonData}\n file:{file}\n 响应数据:{response_result}') return response
执行用例类
@classmethod def execute(cls, data=None, reverse=True): """ 通用执行用例的方法:默认通用断言,断言响应中的success为True :param data: :return: """ # 运行前先判断accessToken cls.__grant_access_token() # 调用方法合并path和method data = cls.basic_attr(data) # 执行用例->所有参数字典丢给send_request处理即可 response, expect, expect_custom = BaseRequest.send_request(data, reverse) # 通用断言 if expect is not None: DataProcess.assert_result(response, expect) # 自定义断言 if expect_custom is not None: DataProcess.expect_keyword(response, expect_custom) return response, expect, expect_custom
tool-字典合并
def merge_dict(dic1, dic2): """ 递归合并两个字典所有数据,有相同的就更新,不相同的就添加 :param dic1: 基本数据 :param dic2: 以dic2数据为准,dic1和dic2都有的数据,合并后以dic2为准 :return: 合并后的字典 """ # 类型不同就直接赋值,返回第2个参数数据,是因为我们以第2个数据为准,来更新第1个数据的。 if type(dic1) != type(dic2): return dic2 # 两个字典深拷贝一下,避免影响之前数据 obj1 = copy.deepcopy(dic1) obj2 = copy.deepcopy(dic2) # 都是字典时处理 if isinstance(obj2, dict): for k, v in obj2.items(): obj1_value = obj1.get(k) if obj1_value is None: obj1[k] = v else: obj1[k] = merge_dict(obj1[k], obj2[k]) elif isinstance(obj2, list): for i in range(len(obj2)): try: obj1[i] = merge_dict(obj1[i], obj2[i]) except IndexError: obj1.append(obj2[i]) elif isinstance(obj2, tuple): for i in range(len(obj2)): try: # 元组不能修改,先转list再修改后再转回元组 obj1 = list(obj1) obj1[i] = merge_dict(obj1[i], obj2[i]) obj1 = tuple(obj1) except IndexError: obj1 += (obj2[i],) else: # 以第2个参数数据为准,返回obj2 return obj2 return obj1
tool-文件处理
import yaml from tools import extractor class ReadFile: """ 文件操作 """ config_dict = None @classmethod def get_config_dict(cls, config_path='config/config.yaml') -> dict: """ 读取配置文件,并且转换成字典,缓存至config_dict :param config_path: yaml文件地址, 默认使用当前项目目录下的config/config.yaml return cls.config_dict """ if cls.config_dict is None: # 指定编码格式解决,win下跑代码抛出错误 with open(config_path, 'r', encoding='utf-8') as file: cls.config_dict = yaml.load( file.read(), Loader=yaml.FullLoader) return cls.config_dict @classmethod def read_config(cls, expr: str = '.'): """ 默认读取config目录下的config.yaml配置文件,根据传递的expr jsonpath表达式可任意返回任何配置项 :param expr: 提取表达式, 使用jsonpath语法,默认值提取整个读取的对象 return 根据表达式返回的值 """ return extractor(cls.get_config_dict(), expr) @classmethod def read_yaml(cls, path): """ 读取yaml文件 :param path: 文件路径 :return: 返回读取的文件数据,dict类型 """ # 指定编码格式解决,win下跑代码抛出错误 with open(path, 'r', encoding='utf-8') as file: data = yaml.load(file.read(), Loader=yaml.FullLoader) return data
用例编写-conftest.py
import pytest @pytest.fixture def test_h5_2xxx(): test_h5_1xxx.execute(data) # 前置执行用例 yield # 以下为后置执行用例 expect = DataProcess.extra_pool.get('expect') data = { "data": { "searchTimeScope": "0", "scope": "2", 'pageType': 'first', 'pageSize': '15' }, 'expect': expect } test_h5_3xxx.execute(data)
用例编写
import pytest from tools import case_data_generate, merge_dict class Test_xxx: @pytest.mark.parametrize("params", case_data_generate(Api_xxx.normal)) # 参数化 def test_xxx( self, params, test_h5_2xxx): # 前置执行conftest中“test_h5_2xxx用例” param, data, expect = params request_data = merge_dict(Api_xxx.params_required, {param: data}) # 提取并合并用例参数 data = { "title": f"第一条用例", "params": request_data, "expect": expect # 断言 } Api_xxx.execute(data) # 用例执行请求
============================= 提升自己 ==========================
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
进群交流、获取更多干货, 请关注微信公众号:

> > > 咨询交流、进群,请加微信,备注来意:sanshu1318 (←点击获取二维码)
> > > 学习路线+测试实用干货精选汇总:
https://www.cnblogs.com/upstudy/p/15859768.html
> > > 【自动化测试实战】python+requests+Pytest+Excel+Allure,测试都在学的热门技术:
https://www.cnblogs.com/upstudy/p/15921045.html
> > > 【热门测试技术,建议收藏备用】项目实战、简历、笔试题、面试题、职业规划:
https://www.cnblogs.com/upstudy/p/15901367.html
> > > 声明:如有侵权,请联系删除。
============================= 升职加薪 ==========================
更多干货,正在挤时间不断更新中,敬请关注+期待。
分类:
C-01【自动化测试】
, C-02→ 接口自动化实战
标签:
自动化测试
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器