Python处理接口数据,这个函数太香了

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 Python处理接口数据 接口数据通常是JSON格式,Python处理起来并不轻松。 方法一,json文件。额外编写代码读取文件,接口参数很多,文件变多,难维护。 方法二,字典。JSON可以直接粘贴到代码中,Python认作字典,但语法有差异,JSON的语法是true/false/null,Python的语法是True/False/None,不兼容。 这两种方法都有共同的缺点,需要编写表达式来定位某个节点。 比如:body["a"][0]["x"]["m"] 字符串与占位符 占位符是一种很常见的文本替换技术,在任何编程语言中都有广泛应用。 Python语法有个很特别的字符串,三引号字符串: test_json = """ { "bool": true, "x": null, "customerId": "123456", "items": [ { "productId": "987654", "quantity": 2, "price": 199.99 } ], "shippingAddress": { "recipientName": "张三", "streetAddress": "北京市朝阳区某街道123号", "city": "北京", "state": "北京", "postalCode": "100000", "country": "中国" }, "paymentMethod": { "type": "CreditCard", "cardNumber": "4111111111111111", "expiryDate": "12/25", "cvv": "123" }, "orderNotes": "请尽快发货,谢谢!" } """ 基本思路是,把JSON写在三引号字符串中,这能保留原生JSON格式,就像很多测试平台的做法一样,然后再通过占位符,直接替换其中的节点值。占位符不用编写表达式定位节点,在JSON字符串标记即可。 ...

2026-01-19 · 2 分钟 · 310 字 · 测试开发刚哥 · 原创项目 · #Tep

年度大版本,tep3回归纯粹Python接口测试框架

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 回顾 tep是基于pytest开发的接口测试框架,上个版本从小工具升级为了关键字驱动框架,通过pystats发现最近半年仍然有4000+人次在安装使用tep。时隔1年后,我对tep进行了一次大版本改造,抛弃tep2的Java代码风格,重新回归到tep1的纯粹Python风格。 纯粹Python 所谓纯粹Python,体现在两个方面,一个方面是tep源码进行了重写,更加Pythonic,自动化用例写起来也更加简单易懂;另一方面是抛弃了依赖Java的Allure和依赖C++的loguru,分别使用Python技术栈的pytest-html和logging替代。 tep3版本 简介 tep是Try Easy Pytest的首字母缩写,帮你轻松上手pytest。 框架特点: 基于pytest封装,成熟、稳定且扩展性强。 框架完全由Python构建,没有混杂其他语言。 原生Python语法,学习Python,零成本使用框架。 HAR包转换pytest用例。 安装 支持Python3.8以上版本 创建虚拟环境:python -m venv venv 激活虚拟环境,Windows用户:activate.bat Mac用户:source venv/bin/activate 安装tep:pip install tep 验证安装成功:tep -V 目录结构 data file fixture report scripts tests .gitignore conftest.py pytest.ini run.py tep new demo就能快速创建项目名称为demo的项目脚手架。 用例示范 简单的请求: from tep import request def test(): response = request("get", url="http://httpbin.org/status/200") assert response.status_code == 200 复杂的场景: from tep import request from tep import v from data.global_data import GlobalData class Data: v({ "domain": GlobalData.domain, "skuNum": 2, "payAmount": 0.2 }) body_add_cart = '{"skuId":"${skuId}","skuNum":${skuNum}}' body_order = '{"skuId":"${skuId}","price":${skuPrice},"skuNum":${skuNum},"totalPrice":${totalPrice}}' body_pay = '{"orderId":"${orderId}","payAmount":${payAmount}}' def test(): url = v("${domain}/searchSku?skuName=book") response = request("get", url=url, headers=GlobalData.headers) assert response.status_code < 400 v("skuId", response.jsonpath("$.skuId")[0]) v("skuPrice", response.jsonpath("$.price")[0]) url = v("${domain}/addCart") body = v(Data.body_add_cart) response = request("post", url=url, headers=GlobalData.headers, json=body) assert response.status_code < 400 v("skuNum", response.jsonpath("$.skuNum")[0]) v("totalPrice", response.jsonpath("$.totalPrice")[0]) url = v("${domain}/order") body = v(Data.body_order) response = request("post", url=url, headers=GlobalData.headers, json=body) assert response.status_code < 400 v("orderId", response.jsonpath("$.orderId")[0]) url = v("${domain}/pay") body = v(Data.body_pay) response = request("post", url=url, headers=GlobalData.headers, json=body) assert response.status_code < 400 assert response.jsonpath("$.success")[0] == "true" 用例和数据 数据怎么存放,这里给出了一个参考方案,在用例内定义class Data,然后通过类变量存放数据。不同的项目,不同的场景,不同的参数,可以考虑不一样的存放方式。 ...

2026-01-18 · 2 分钟 · 296 字 · 测试开发刚哥 · 原创项目 · #Tep

tep1.0.0教程

正式版教程: https://dongfanger.gitee.io/blog/tep.html

2026-01-17 · 8 分钟 · 1666 字 · 测试开发刚哥 · 原创项目 · #Tep

当Pytest遇上MVC分层设计自动化用例就该这么写

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 引子 数据写在代码里,追求快速编写用例,是我设计tep的一个特点,这在个人编写时是一种非常良好的体验。但相比于HttpRunner、JMeter等来说,总觉得还差点意思。思考良久,总结为三个字:工程化。工程化是我近一年在学习Java并参与了2个测试平台模块开发,和写了几个小工具后,感受到的一种编程思想。而其中最明显的就是Spring的MVC分层设计。为了让tep更工程化,后续版本将以MVC模块编写用例为准,同时会兼容之前的脚本式快速编写。 示例 目录结构 测试用例都放在一个文件夹下: test_case:用例主程序; steps:测试步骤; data:纯粹的json; 测试用例 test_case.py是测试用例,包含的只有测试步骤: import allure from examples.tests.LoginToPayFlow.steps.step_add_cart import step_add_cart from examples.tests.LoginToPayFlow.steps.step_order import step_order from examples.tests.LoginToPayFlow.steps.step_pay import step_pay from examples.tests.LoginToPayFlow.steps.step_search_sku import step_search_sku from utils.cache import TepCache from utils.step import Step """ 测试登录到下单流程,需要先运行utils/fastapi_mock.py """ @allure.title("从登录到下单支付") def test(login, env_vars, case_vars): case_vars.put("token", login["token"]) cache = TepCache(env_vars=env_vars, case_vars=case_vars) Step("搜索商品", step_search_sku, cache) Step("添加购物车", step_add_cart, cache) Step("下单", step_order, cache) Step("支付", step_pay, cache) TepCache是“缓存”,包括global_vars、env_vars、case_vars三个级别的变量池。 Step是一个泛化调用类,作用是打日志,调用第二个参数对应的步骤函数。 测试步骤 from utils.cache import TepCache from utils.func import data from utils.http_client import request def step_search_sku(cache: TepCache): url = cache.env_vars["domain"] + "/searchSku" headers = {"token": cache.case_vars.get("token")} body = data("查询SKU.json") response = request("get", url=url, headers=headers, params=body) assert response.status_code < 400 cache.case_vars.put("skuId", response.jsonpath("$.skuId")) cache.case_vars.put("skuPrice", response.jsonpath("$.price")) from utils.cache import TepCache from utils.func import data from utils.http_client import request def step_add_cart(cache: TepCache): url = cache.env_vars["domain"] + "/addCart" headers = {"token": cache.case_vars.get("token")} body = data("添加购物车.json") body["skuId"] = cache.case_vars.get("skuId") response = request("post", url=url, headers=headers, json=body) assert response.status_code < 400 cache.case_vars.put("skuNum", response.jsonpath("$.skuNum")) cache.case_vars.put("totalPrice", response.jsonpath("$.totalPrice")) 步骤函数以step开头,尤其注意的是cache借助Python Typing提示,可以在编写下面代码时,获得PyCharm语法提示,所以一定不要忘了加上: TepCache: ...

2026-01-16 · 3 分钟 · 492 字 · 测试开发刚哥 · 原创项目 · #Tep

tep支持pytest-xdist分布式执行用例及合并Allure报告

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 tep近期更新频率较快,一方面是作者在积极投入到tep工具开发中;另一方面是我们聚集了20位小伙伴,一起合力打造EasyPytest测试平台,teprunner的FastAPI升级版本,依托于tep,帮你高效管理pytest测试用例。陆续也会有平台开发日志发布,欢迎持续关注。 tep0.11.1版本升级内容: 1、pytest-xdist分布式执行用例,合并Allure报告; 2、global_vars全局变量配置; 分布式执行用例 借助于pytest-xdist,在命令行执行时添加参数-n auto: pytest -n auto pytest-xdist会自动根据本地机器硬件配置,设置最优并发,并分发用例,分布式执行。 测试时先运行main.py,然后在case100目录下执行pytest -n auto --tep-reports: 第一次串行,第二次xdist并行: 执行时间从50s一下降到5s,性能提升还是非常的明显。 合并Allure报告 pytest-xdist分布式执行,只要把allure源文件,也就是那一堆json文件,存到同一个目录下,报告的数据就是一体的,不需要单独合并。但是有个问题,tep封装了--tep-reports 命令行参数一键生成Allure报告,背后的逻辑是在pytest_sessionfinish hook函数里面实现的,分布式执行时,每个xdist的node都是一个单独的session,多个node就会生成多份报告: c 10个node生成了11份报告,其中1份master节点生成的。pytest-xdist的原理如图所示: master节点不运行任何测试,只是通过一小部分消息与节点通信。子节点执行后会通过workeroutput把数据回传给master节点。所以只需要通过是否有workeroutput属性来判断master节点: def _is_master(config): """ pytest-xdist分布式执行时,判断是主节点master还是子节点 主节点没有workerinput属性 """ return not hasattr(config, 'workerinput') 然后只在主节点的pytest_sessionfinish,生成1次报告,就能避免生成多份报告。这样在xdist分布式执行模式下,--tep-reports也只会生成1份合并后的包含所有测试用例的Allure HTML报告。 完整实现代码: import time import shutil import allure_commons from allure_commons.logger import AllureFileLogger from allure_pytest.listener import AllureListener from allure_pytest.plugin import cleanup_factory reports_path = os.path.join(Project.root_dir, "reports") allure_source_dir_name = ".allure.source.temp" allure_source_path = os.path.join(reports_path, allure_source_dir_name) def _tep_reports(config): """ --tep-reports命令行参数不能和allure命令行参数同时使用,否则可能出错 """ if config.getoption("--tep-reports") and not config.getoption("allure_report_dir"): return True return False def _is_master(config): """ pytest-xdist分布式执行时,判断是主节点master还是子节点 主节点没有workerinput属性 """ return not hasattr(config, 'workerinput') def pytest_addoption(parser): """ allure测试报告 命令行参数 """ parser.addoption( "--tep-reports", action="store_const", const=True, help="Create tep allure HTML reports." ) def pytest_configure(config): """ 这段代码源自:https://github.com/allure-framework/allure-python/blob/master/allure-pytest/src/plugin.py 目的是生成allure源文件,用于生成HTML报告 """ if _tep_reports(config): if os.path.exists(allure_source_path): shutil.rmtree(allure_source_path) test_listener = AllureListener(config) config.pluginmanager.register(test_listener) allure_commons.plugin_manager.register(test_listener) config.add_cleanup(cleanup_factory(test_listener)) clean = config.option.clean_alluredir file_logger = AllureFileLogger(allure_source_path, clean) # allure_source allure_commons.plugin_manager.register(file_logger) config.add_cleanup(cleanup_factory(file_logger)) def pytest_sessionfinish(session): """ 测试运行结束后生成allure报告 """ if _tep_reports(session.config): if _is_master(session.config): # 只在master节点才生成报告 # 最近一份报告的历史数据,填充allure趋势图 if os.path.exists(reports_path): his_reports = os.listdir(reports_path) if allure_source_dir_name in his_reports: his_reports.remove(allure_source_dir_name) if his_reports: latest_report_history = os.path.join(reports_path, his_reports[-1], "history") shutil.copytree(latest_report_history, os.path.join(allure_source_path, "history")) current_time = time.strftime("%Y-%m-%d-%H-%M-%S", time.localtime(time.time())) html_report_name = os.path.join(reports_path, "report-" + current_time) os.system(f"allure generate {allure_source_path} -o {html_report_name} --clean") shutil.rmtree(allure_source_path) global_vars全局变量 在使用环境变量模版时,有些变量需要在多个模版重复设置,tep新增了global_vars全局变量fixture,可以将重复设置的变量,定义在resources/global_vars.yaml: ...

2026-01-15 · 2 分钟 · 220 字 · 测试开发刚哥 · 原创项目 · #Tep

tep时隔8个月迎来重大全新升级

tep此次更新,旨在从“工具”升级为“框架”,为此做了大量的代码整洁工作,重新设计了部分功能,项目脚手架也焕然一新。

2026-01-14 · 2 分钟 · 234 字 · 测试开发刚哥 · 原创项目 · #Tep

接口自动化项目落地之HTTPBin网站

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 接口自动化项目落地系列 找个开源网站或开源项目,用tep实现整套pytest接口自动化项目落地,归档到电子书,作为tep完整教程的项目篇一部分。自从tep完整教程发布以后,tep被越来越多小伙伴了解。教程只是纯理论,是骡子是马,拉出来遛遛才知道。做接口自动化项目落地,一方面是为了让自己脑海中的构想实实在在的呈现出来,现实和理想存在多少差距,不断尝试去弥补和修缮;另一方面也是方便读者朋友们学习使用,借助实际项目来练习,才能在赛道中弯道超车。 HTTPBin网站 httpbin.org是一个简单的在线提供HTTP服务的网站: 它能够用来对HTTP进行在线测试。 测试报告 HTTPBin网站的接口自动化项目包含11个用例集: 67条测试用例: 自动化执行正确率98.5%,其中有1条错误结果,是我故意为之的,因为想展示下断言失败的效果。 环境配置 包含http和https两套环境,因为HTTPBin支持HTTP&HTTPS: fixtures/fixture_env_vars.py #!/usr/bin/python ## encoding=utf-8 from tep.fixture import * @pytest.fixture(scope="session") def env_vars(config): class Clazz(TepVars): env = config["env"] """变量定义开始""" # 环境变量 mapping = { "http": { # http环境 "domain": "http://httpbin.org", }, "https": { # https环境 "domain": "https://httpbin.org", } # 继续添加 } # 定义类属性,敲代码时会自动补全 domain = mapping[env]["domain"] """变量定义结束""" return Clazz() 配置默认为http环境: conf.yaml env: http 用例集 http-methods import allure from tep.client import request @allure.title("get请求") def test(env_vars): # 描述 # 数据 # 请求 response = request( "get", url=env_vars.domain + "/get", headers={'Host': 'httpbin.org', 'Proxy-Connection': 'keep-alive', 'accept': 'application/json', 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.109 Safari/537.36', 'Referer': 'http://httpbin.org/', 'Accept-Encoding': 'gzip, deflate', 'Accept-Language': 'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7'}, params={} ) # 提取 # 断言 assert response.status_code < 400 auth ...

2026-01-13 · 5 分钟 · 1031 字 · 测试开发刚哥 · 原创项目 · #Tep

民间流转的pytest项目结构设计

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 创建上图的pytest项目结构,只需要一条命令即可: tep startproject demo 前提是安装tep0.9.8版本: pip install tep==0.9.8 示例代码单独放在了samples文件夹下,可以放心将整个目录删除。其中的db演示了连接MySQL数据库,http演示了requests常见用法和tep request的猴子补丁,login_pay演示了从登陆到下单该如何编写脚本,包括httprunner、mvc(不推荐)、tep(强烈推荐)三种写法。 fixtures从conftest.py文件中单独拎了出来。这个设计与Django不谋而合,Django也有个叫做fixtures的文件夹,用来存放测试数据。其实pytest的fixture的本意是可以用来做setup和teardown的,从这个意义来说,fixtures就是存放测试数据的,比如封装登录接口为用例提供token。 utils存放了一些工具。比如自带的fastapi项目,request猴子补丁,mitmproxy抓包自动生成用例。request猴子补丁可以自定义日志输出格式,在发送请求前后完成任何你想做的操作。 tep0.9.8版本正式发布,对项目脚手架进行了精简,是为接下来的**《tep完整教程帮你突破pytest》**做好准备。

2026-01-12 · 1 分钟 · 14 字 · 测试开发刚哥 · 原创项目 · #Tep

teprunner测试平台入门级部署手册发布啦

很多朋友是因为teprunner,也就是这个小众的pytest内核测试平台关注的公众号。为了让大家更好的上手teprunner,我更新了它的README,希望能让小伙伴们根据这些文档内容,一步一步的在自己本地电脑上把项目跑起来。项目跑起来之后,就可以参考前面一系列的学习教程,自己动手做一遍,在做的过程中和teprunner进行对比,不懂的点逐一突破,由点到面,完整实现。**这种学习方式能更快速的掌

tep集成mitmproxy录制流量自动生成用例

本书作者:测试开发刚哥,文章版权归个人所有,未经授权请勿转载。 使用 操作过程非常简单,流程如下: ①配置过滤域名 必须配置,不然会有很多无效流量造成数据冗余。 ②启动代理 「示例」使用了反向代理,tep自带FastApi启动Mock服务: 「实际」使用正向代理,并手动配置代理。命令说明在utils/mitm.py文件中有: ③录制流量 「示例」通过Postman发起请求: 「实际」在网页上点点点。 自动生成用例 用例就在tests/mitm实时生成好了,用例文件名为当前时间: 每录制一个请求,就能在测试用例中看到实时添加了一条测试步骤: # 接口描述 # 数据 # 请求 response = request( "{method}", url="{url}", headers={headers}, {body_grammar} ) # 数据提取 # var = response.jmespath("expression") # 断言 assert response.status_code < 400 录制完成以后,就可以再次编辑测试用例完善它。 原理 utils/mitm.py源码如下: #!/usr/bin/python ## encoding=utf-8 ## mitmproxy录制流量自动生成用例 import os import time from mitmproxy import ctx project_dir = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) tests_dir = os.path.join(project_dir, "tests") ## tests/mitm mitm_dir = os.path.join(tests_dir, "mitm") if not os.path.exists(mitm_dir): os.mkdir(mitm_dir) ## 当前时间作为文件名 filename = time.strftime("%Y%m%d_%H_%M_%S", time.localtime()) + ".py" case_file = os.path.join(mitm_dir, filename) ## 生成用例文件 template = """from tep.client import request def test(): """ if not os.path.exists(case_file): with open(case_file, "w", encoding="utf8") as fw: fw.write(template) class Record: def __init__(self, domains): self.domains = domains def response(self, flow): if self.match(flow.request.url): # method method = flow.request.method.lower() ctx.log.error(method) # url url = flow.request.url ctx.log.error(url) # headers headers = dict(flow.request.headers) ctx.log.error(headers) # body body = flow.request.text or {} ctx.log.error(body) with open(case_file, "a", encoding="utf8") as fa: fa.write(self.step(method, url, headers, body)) def match(self, url): if not self.domains: ctx.log.error("必须配置过滤域名") exit(-1) for domain in self.domains: if domain in url: return True return False def step(self, method, url, headers, body): if method == "get": body_grammar = f"params={body}" else: body_grammar = f"json={body}" return f""" # 接口描述 # 数据 # 请求 response = request( "{method}", url="{url}", headers={headers}, {body_grammar} ) # 数据提取 # var = response.jmespath("expression") # 断言 assert response.status_code < 400 """ ## ==================================配置开始================================== addons = [ Record( # 过滤域名 [ "http://www.httpbin.org", "http://127.0.0.1:5000" ], ) ] ## ==================================配置结束================================== """ ==================================命令说明开始================================== ## 正向代理 mitmdump -s mitm.py ## 反向代理 mitmdump -s mitm.py --mode reverse:http://127.0.0.1:5000 --listen-host 127.0.0.1 --listen-port 8000 ==================================命令说明结束================================== """ 通过mitmproxy命令启动代理后,获取当前时间作为文件名在tests/mitm下生成用例文件,给addons添加插件Record类,通过response钩子捕获请求信息,对请求进行域名过滤,域名匹配成功的请求就使用测试步骤代码模板,填充method、url、headers、body数据,追加到测试用例文件中。 ...

2026-01-11 · 2 分钟 · 267 字 · 测试开发刚哥 · 原创项目 · #Tep