ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Pytest、JMeter与SQL脚本生成执行的一体化skill工具箱实战

Pytest、JMeter与SQL脚本生成执行的一体化skill工具箱实战 日常开发里接口自动化测试、性能测试和测试数据准备是三块绕不开的工作。新建一个接口要写 pytest 用例要做压测得打开 JMeter 配置线程组、HTTP 请求、监听器要准备测试数据又得手写 SQL。这些工作本身不复杂但重复多了效率很低也容易出错。随着 AI 辅助编程越来越普及很多人会直接让大模型生成测试脚本初稿。可生成完之后经常卡在环境变量、参数替换、执行方式这些细节上AI 生成的内容反而成了“半成品”。本文分享一套把 Pytest、JMeter、SQL 脚本生成与执行整合在一起的 skill 工具箱模板驱动、配置隔离、环境自检、一键执行。文章会从目录结构、核心代码到运行效果完整拆开你可以直接复制到项目中使用。1. 背景与核心概念1.1 为什么需要一套脚本工具箱先看几个真实场景。接口自动化测试中一个项目往往有成百上千个接口。如果每个接口都手工写一份requests pytest代码代码重复率很高后期接口地址、请求头、断言逻辑一变就要全局改动。更常见的是新增一批测试数据又要在测试库里执行大量 SQL这些 SQL 语句散落在不同人的笔记里很难统一管理。性能测试的场景更明显。JMeter 脚本本质上是一个 JMX XML 文件手工打开 GUI 配置线程组、Sampler、监听器步骤多且不容易复用。如果压测环境从测试环境切到预发环境又要手动改一堆参数。这三类脚本都满足“高频、重复、有规律”的特征。高频的事情一旦有规律就非常适合模板化、参数化、自动化。工具箱要做的就是把这些重复动作沉淀为模板和执行器让开发者从“手工写脚本”变成“配置参数、自动生成、一键执行”。1.2 Pytest、JMeter、SQL 分别解决什么问题Pytest是目前 Python 生态中最主流的测试框架之一。它支持fixture数据管理、parametrize参数化、插件扩展也可以生成 JUnitXML、Allure 报告非常适合接口自动化测试。对于一套自动生成脚本的工具箱来说pytest 承担的是“接口用例生成与执行”的角色。JMeter是开源性能测试工具通过 JMX 文件描述测试计划。JMeter 可以用命令行一键执行不需要打开 GUI 就能完成压测并输出 JTL 结果和 HTML 报告。在自动生成脚本的场景里JMeter 承担的是“性能测试脚本生成与执行”的角色。SQL是数据库操作语言。测试工程里经常需要准备测试数据、初始化表数据、校验数据一致性有时还要做一些批量更新操作。SQL 脚本生成器承担的是“测试数据脚本生成”的角色。这三个工具解决的问题不同但在工程链路里经常一起出现先用 SQL 准备测试数据再用 pytest 验证接口功能最后用 JMeter 做性能压测。1.3 “skill 工具箱”是什么“skill”在这里更接近“技能沉淀”的意思不是指某个特定语言里的 skill 脚本而是把个人或团队的最佳实践封装成模板和工具。结合现在很流行的 AI 辅助生成脚本方式这套工具箱的定位是AI 负责把需求描述成模板内容工具箱负责把模板内容渲染成可运行脚本并完成环境自检、命令执行和报告输出。也就是说你不用每次打开编辑器从零写测试用例也不用每次都手动打开 JMeter 点界面。只要维护好config.yaml和templates/目录下的模板调用统一入口就能批量产出脚本。2. 环境准备与版本说明2.1 软件依赖清单本文示例以本地开发环境为主操作系统可以是 Windows、macOS 或 Linux。不同系统的命令略有差异但整体思路一致。软件版本建议用途Python3.10 及以上运行 pytest 和工具箱主程序pytest7.x 及以上执行接口测试用例requests2.xpytest 用例发送 HTTP 请求PyYAML6.x 及以上读取 config.yaml 配置文件JDK8 或 11JMeter 运行依赖JMeter5.x性能测试执行数据库客户端按实际库选择验证生成 SQL 的可执行性版本不需要完全一致重点是把环境变量配置好。如果你本地已经有 Python 和 JMeter直接继续往下走即可。2.2 检查命令是否在 PATH 中很多初学者遇到的一个典型报错是jmeter : 无法将“jmeter”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。 pytest : 无法将“pytest”项识别为 cmdlet、函数、脚本文件或可运行程序的名称。这个问题的本质是命令所在目录没有加入系统 PATH 环境变量。类似的报错也会出现在npm、pnpm、opencode、claude这些工具的安装过程中解决方法是一样的。在 Windows 上可以打开“系统属性 - 环境变量”找到Path变量把对应工具的bin目录或 Python 的Scripts目录添加进去。在 macOS/Linux 上通常在~/.zshrc或~/.bashrc中追加export PATH工具目录:$PATH。工具箱里会实现一个check_env()函数自动检测当前环境缺失哪些命令避免你运行到一半才发现工具没装好。2.3 项目目录结构整套工具箱建议按下面的目录组织skillkit/ ├── config.yaml ├── requirements.txt ├── main.py ├── core/ │ ├── __init__.py │ ├── env_check.py │ ├── report.py │ └── utils.py ├── modules/ │ ├── __init__.py │ ├── pytest_runner.py │ ├── jmeter_runner.py │ └── sql_generator.py ├── templates/ │ ├── pytest_template.py │ └── jmeter_template.jmx ├── gen_scripts/ └── reports/core/放公共能力例如环境检测、模板渲染、报告生成modules/放三个执行器templates/放脚本模板gen_scripts/存放自动生成出来的脚本reports/存放测试报告。3. 工具箱整体架构设计3.1 设计目标这套工具箱的设计目标可以概括为四点统一入口只需要一个main.py通过--tool参数控制执行 pytest、jmeter 还是 sql。模板驱动脚本内容不写在业务代码里而是放在templates/目录通过占位符替换生成。环境自检执行前先检查依赖命令是否存在提前暴露问题。一键执行生成脚本后直接调用对应命令行工具返回结果并输出报告。3.2 模块划分模块划分很简单模块职责core/env_check.py检测 pytest、jmeter 等命令是否可用core/utils.py提供模板渲染公共函数core/report.py生成 Markdown 汇总报告modules/pytest_runner.py根据配置生成 pytest 脚本并执行modules/jmeter_runner.py根据模板生成 JMX 脚本并调用 jmetermodules/sql_generator.py根据配置生成 SQL 脚本3.3 调用流程整体调用流程可以用下面这张有序列表描述读取config.yaml获取当前工具需要的参数。调用check_env()检查相关命令是否在 PATH 中。读取templates/下的模板文件。使用配置参数替换模板中的{{占位符}}。将渲染后的脚本写入gen_scripts/。调用 pytest、jmeter 等命令执行脚本。将执行结果汇总到reports/并打印关键信息。4. 核心模块实现4.1 统一配置 config.yaml配置文件是整个工具箱的“数据源”。我们把 pytest、jmeter、sql 三部分参数都放在同一个 YAML 文件里方便集中管理。# 文件路径config.yaml pytest: base_url: http://127.0.0.1:8080 timeout: 10 cases: - name: 查询用户 method: GET path: /api/user expected_status: 200 - name: 创建订单 method: POST path: /api/order data: userId: 1 expected_status: 200 jmeter: protocol: http host: 127.0.0.1 port: 8080 path: /api/order method: POST threads: 50 ramp_time: 10 loop_count: 5 sql: statements: - type: select table: t_user columns: id, name, status where: status active limit: 100 - type: insert table: t_user values: name: 张三 status: 1 - type: update table: t_user set: status: 2 where: id 1001 - type: delete table: t_user where: id 1001使用 YAML 的好处是结构清晰、可读性强适合表达多级嵌套的数据。cases列表可以随意增删每一条都对应一个 pytest 用例。4.2 公共工具与环境自检公共渲染函数放在core/utils.py中。这里采用{{占位符}}方式替换而不是 Python 的format()或string.Template主要原因是它不会和 Python 代码里的花括号、JMeter 变量语法冲突。# 文件路径core/utils.py def render_template(template_text: str, values: dict) - str: for key, value in values.items(): template_text template_text.replace({{ key }}, str(value)) return template_text环境自检模块放在core/env_check.py# 文件路径core/env_check.py import shutil def check_env(): required { python: Python 解释器, pytest: pytest 测试框架, jmeter: JMeter 性能测试工具, sqlite3: SQLite 客户端, } missing [] for cmd in required: if shutil.which(cmd): print(f[ENV] {cmd}: {shutil.which(cmd)}) else: missing.append(cmd) if missing: print([WARN] 以下命令未找到请安装后加入系统 PATH 环境变量) for cmd in missing: print(f - {cmd}: {required[cmd]}) return missing这个函数会遍历一批命令逐个检查是否存在于 PATH 中。jmeter没安装时会给 WARN但不会阻断 pytest 等其他子命令的运行。这样设计是为了让用户能先跑通一部分功能避免“一票否决”。4.3 Pytest 脚本生成与执行模块pytest 脚本模板放在templates/pytest_template.py# 文件路径templates/pytest_template.py # -*- coding: utf-8 -*- 自动生成的 Pytest 接口测试脚本由模板渲染 import requests import pytest BASE_URL {{BASE_URL}} TIMEOUT int({{TIMEOUT}}) CASES {{CASES}} pytest.mark.parametrize(case, CASES, ids[c[name] for c in CASES]) def test_api(case): url BASE_URL.rstrip(/) case[path] method case[method].upper() headers case.get(headers, {}) data case.get(data) resp requests.request(method, url, headersheaders, jsondata, timeoutTIMEOUT) assert resp.status_code case.get(expected_status, 200), \ f状态码异常: {resp.status_code}, body{resp.text}模板中使用{{BASE_URL}}、{{TIMEOUT}}、{{CASES}}三个占位符。CASES的位置会替换成一段 Python 列表字面量因此支持参数化执行。执行器放在modules/pytest_runner.py# 文件路径modules/pytest_runner.py import pprint import subprocess import sys from pathlib import Path import yaml from core.utils import render_template class PytestRunner: def __init__(self, config_pathconfig.yaml, output_dirgen_scripts): self.config_path config_path self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def load_config(self): with open(self.config_path, encodingutf-8) as f: return yaml.safe_load(f)[pytest] def generate(self, cfg): template Path(templates/pytest_template.py).read_text(encodingutf-8) case_repr pprint.pformat(cfg[cases], indent4, width120) values { BASE_URL: cfg[base_url], TIMEOUT: cfg[timeout], CASES: case_repr, } code render_template(template, values) script_path self.output_dir / test_api_auto.py script_path.write_text(code, encodingutf-8) return script_path def run(self): cfg self.load_config() script_path self.generate(cfg) report Path(reports/pytest_result.xml) report.parent.mkdir(parentsTrue, exist_okTrue) cmd [ sys.executable, -m, pytest, str(script_path), -v, --junitxml, str(report), ] proc subprocess.run(cmd, capture_outputTrue, textTrue, encodingutf-8) print(proc.stdout) if proc.returncode ! 0: print(proc.stderr) return { tool: pytest, report: str(report), returncode: proc.returncode, }这里有几个细节值得说明使用pprint.pformat()把配置中的 cases 转成 Python 可解析的源码而不是直接使用json.dumps()因为 JSON 里的null、true与 Python 的None、True并不等价。命令使用sys.executable -m pytest即使 pytest 不在 PATH 中也能通过当前 Python 解释器执行。报告输出为 JUnitXML 格式方便后续接入 CI 平台。4.4 JMeter 脚本生成与执行模块JMeter 的 JMX 文件本质是 XML。为了让生成的 JMX 可以稳定运行建议先在本机 JMeter GUI 里手动创建一个包含 HTTP 请求的测试计划另存为模板再把需要动态变化的参数改成占位符。下面是一个简化版 JMX 模板重点展示参数替换思路!-- 文件路径templates/jmeter_template.jmx -- ?xml version1.0 encodingUTF-8? jmeterTestPlan version1.2 properties5.0 jmeter5.6.3 hashTree TestPlan guiclassTestPlanGui testclassTestPlan testnameAPI Load Test enabledtrue stringProp nameTestPlan.comments/stringProp boolProp nameTestPlan.functional_modefalse/boolProp boolProp nameTestPlan.serialize_threadgroupsfalse/boolProp elementProp nameTestPlan.user_defined_variables elementTypeArguments guiclassArgumentsPanel testclassArguments testnameUser Defined Variables enabledtrue collectionProp nameArguments.arguments/ /elementProp /TestPlan hashTree ThreadGroup guiclassThreadGroupGui testclassThreadGroup testnameThread Group enabledtrue stringProp nameThreadGroup.on_sample_errorcontinue/stringProp elementProp nameThreadGroup.main_controller elementTypeLoopController guiclassLoopControlPanel testclassLoopController testnameLoop Controller enabledtrue boolProp nameLoopController.continue_foreverfalse/boolProp stringProp nameLoopController.loops{{LOOP_COUNT}}/stringProp /elementProp stringProp nameThreadGroup.num_threads{{THREAD_COUNT}}/stringProp stringProp nameThreadGroup.ramp_time{{RAMP_TIME}}/stringProp boolProp nameThreadGroup.schedulerfalse/boolProp stringProp nameThreadGroup.duration/stringProp stringProp nameThreadGroup.delay/stringProp /ThreadGroup hashTree HTTPSamplerProxy guiclassHttpTestSampleGui testclassHTTPSamplerProxy testnameHTTP Request enabledtrue elementProp nameHTTPsampler.Arguments elementTypeArguments guiclassHTTPArgumentsPanel testclassArguments testnameUser Defined Variables enabledtrue collectionProp nameArguments.arguments/ /elementProp stringProp nameHTTPSampler.domain{{TARGET_HOST}}/stringProp stringProp nameHTTPSampler.port{{TARGET_PORT}}/stringProp stringProp nameHTTPSampler.protocol{{TARGET_PROTOCOL}}/stringProp stringProp nameHTTPSampler.path{{TARGET_PATH}}/stringProp stringProp nameHTTPSampler.method{{HTTP_METHOD}}/stringProp boolProp nameHTTPSampler.follow_redirectstrue/boolProp /HTTPSamplerProxy hashTree/ /hashTree /hashTree /hashTree /jmeterTestPlanJMeter 执行器# 文件路径modules/jmeter_runner.py import shutil import subprocess from pathlib import Path import yaml from core.utils import render_template class JmeterRunner: def __init__(self, config_pathconfig.yaml, output_dirgen_scripts): self.config_path config_path self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def load_config(self): with open(self.config_path, encodingutf-8) as f: return yaml.safe_load(f)[jmeter] def generate_jmx(self, cfg): template Path(templates/jmeter_template.jmx).read_text(encodingutf-8) values { TARGET_PROTOCOL: cfg[protocol], TARGET_HOST: cfg[host], TARGET_PORT: str(cfg[port]), TARGET_PATH: cfg[path], HTTP_METHOD: cfg[method], THREAD_COUNT: str(cfg[threads]), RAMP_TIME: str(cfg[ramp_time]), LOOP_COUNT: str(cfg[loop_count]), } jmx_text render_template(template, values) jmx_path self.output_dir / api_load_test.jmx jmx_path.write_text(jmx_text, encodingutf-8) return jmx_path def run(self): if not shutil.which(jmeter): print([ERROR] 未检测到 jmeter 命令请先安装 JMeter 并配置 PATH) return {tool: jmeter, report: , returncode: 1} cfg self.load_config() jmx_path self.generate_jmx(cfg) reports_dir Path(reports) reports_dir.mkdir(parentsTrue, exist_okTrue) jtl_path reports_dir / jmeter_result.jtl html_dir reports_dir / jmeter_html if jtl_path.exists(): jtl_path.unlink() if html_dir.exists(): import shutil as sh sh.rmtree(html_dir) cmd [ jmeter, -n, -t, str(jmx_path), -l, str(jtl_path), -e, -o, str(html_dir), ] proc subprocess.run(cmd, capture_outputTrue, textTrue, encodingutf-8) print(proc.stdout) if proc.returncode ! 0: print(proc.stderr) return { tool: jmeter, report: str(html_dir), returncode: proc.returncode, }生成 JMX 后工具箱会调用 JMeter 的非 GUI 模式执行。-n表示非 GUI-t指定测试计划-l输出结果文件-e -o生成 HTML 报告。考虑到重复压测场景执行前会先清理旧的 JTL 和 HTML 目录避免报告被追加或覆盖失败。4.5 SQL 脚本生成与执行模块SQL 生成器不只是简单拼接字符串它内置了三个安全约束UPDATE和DELETE必须携带 WHERE 条件。检测常见 SQL 注入特征词。所有 INSERT/UPDATE 使用%s占位符避免把值直接拼接进 SQL。# 文件路径modules/sql_generator.py from pathlib import Path import yaml class SQLGenerator: def __init__(self, config_pathconfig.yaml, output_dirgen_scripts): self.config_path config_path self.output_dir Path(output_dir) self.output_dir.mkdir(parentsTrue, exist_okTrue) def load_config(self): with open(self.config_path, encodingutf-8) as f: return yaml.safe_load(f).get(sql, {}) staticmethod def check_dangerous(sql: str): low sql.lower() dangerous [or 11, union select, drop table, truncate] for keyword in dangerous: if keyword in low: raise ValueError(f检测到疑似 SQL 注入模式: {keyword}) staticmethod def require_where(sql_type: str, where_clause: str): if sql_type in (UPDATE, DELETE) and not where_clause.strip(): raise ValueError(f{sql_type} 语句必须携带 WHERE 条件) def generate_select(self, table, columns, whereNone, limitNone): sql fSELECT {columns} FROM {table} if where: sql f WHERE {where} if limit: sql f LIMIT {limit} self.check_dangerous(sql) return sql ; def generate_insert(self, table, values): cols , .join(values.keys()) placeholders , .join([%s] * len(values)) sql fINSERT INTO {table} ({cols}) VALUES ({placeholders}); params , .join(repr(v) for v in values.values()) return sql f -- 待绑定参数: {params} def generate_update(self, table, set_values, where): self.require_where(UPDATE, where) assignments , .join(f{col} %s for col in set_values.keys()) sql fUPDATE {table} SET {assignments} WHERE {where} self.check_dangerous(sql) return sql ; def generate_delete(self, table, where): self.require_where(DELETE, where) sql fDELETE FROM {table} WHERE {where} self.check_dangerous(sql) return sql ; def run(self): cfg self.load_config() statements [] for item in cfg.get(statements, []): stype item[type] if stype select: statements.append( self.generate_select( item[table], item.get(columns, *), item.get(where), item.get(limit), ) ) elif stype insert: statements.append(self.generate_insert(item[table], item[values])) elif stype update: statements.append( self.generate_update(item[table], item[set], item[where]) ) elif stype delete: statements.append(self.generate_delete(item[table], item[where])) script \n.join(statements) sql_path self.output_dir / biz_script.sql sql_path.write_text(script, encodingutf-8) print(script) return { tool: sql, report: str(sql_path), returncode: 0, }如果你运行上面的配置biz_script.sql中会生成类似下面的内容SELECT id, name, status FROM t_user WHERE status active LIMIT 100; INSERT INTO t_user (name, status) VALUES (%s, %s); -- 待绑定参数: 张三, 1 UPDATE t_user SET status %s WHERE id 1001; DELETE FROM t_user WHERE id 1001;这里要强调一个安全观点任何动态拼接的 SQL 都存在注入风险。check_dangerous()只是兜底手段真正安全的做法是使用参数化查询或预处理语句。生产环境中不允许把用户输入直接拼接到 SQL 中。4.6 统一入口 main.py最后把三个模块串起来。main.py支持--tool参数指定执行哪个工具也支持--all依次执行三个工具。# 文件路径main.py import argparse import sys from pathlib import Path ROOT Path(__file__).resolve().parent sys.path.insert(0, str(ROOT)) from core.env_check import check_env from core.report import generate_summary from modules.jmeter_runner import JmeterRunner from modules.pytest_runner import PytestRunner from modules.sql_generator import SQLGenerator def run_tool(tool: str, config_path: str, output_dir: str) - dict: if tool pytest: return PytestRunner(config_path, output_dir).run() if tool jmeter: return JmeterRunner(config_path, output_dir).run() if tool sql: return SQLGenerator(config_path, output_dir).run() raise ValueError(f未知工具: {tool}) def main(): parser argparse.ArgumentParser(descriptionPytest/JMeter/SQL 脚本工具箱) parser.add_argument(--tool, choices[pytest, jmeter, sql], help要执行的工具) parser.add_argument(--all, actionstore_true, help依次执行 pytest、jmeter、sql) parser.add_argument(--config, defaultconfig.yaml, help配置文件路径) parser.add_argument(--output, defaultgen_scripts, help脚本输出目录) args parser.parse_args() check_env() tools [pytest, jmeter, sql] if args.all else [args.tool] if not tools or not all(tools): parser.print_help() return results [] for tool in tools: print(f\n 开始执行: {tool} ) results.append(run_tool(tool, args.config, args.output)) summary generate_summary(results) print(f\n[OK] 报告已生成: {summary}) if __name__ __main__: main()汇总报告模块# 文件路径core/report.py from datetime import datetime from pathlib import Path def generate_summary(results) - Path: report Path(reports/summary.md) report.parent.mkdir(parentsTrue, exist_okTrue) lines [ # 脚本执行汇总报告\n, f- 生成时间{datetime.now().strftime(%Y-%m-%d %H:%M:%S)}\n, ] for result in results: lines.append(f- 工具{result[tool]}返回码{result[returncode]}产物{result[report]}) report.write_text(\n.join(lines), encodingutf-8) return report5. 运行与验证5.1 安装依赖并初始化环境在项目根目录执行pip install -r requirements.txtrequirements.txt内容如下pytest7.0.0 requests2.28.0 PyYAML6.0安装完成后先运行一次环境检查python main.py --help如果能打印出帮助信息说明主程序可以正常加载。5.2 一键生成 Pytest 接口用例执行python main.py --tool pytest程序会读取config.yaml中的 cases生成gen_scripts/test_api_auto.py然后自动调用 pytest 执行。如果本地 8080 端口没有启动对应服务pytest 用例会失败这是环境问题不是脚本问题。你可以先把base_url改成被测环境地址。如果被测服务已启动预期输出类似collecting ... collected 2 items test_api_auto.py::test_api[查询用户] PASSED [ 50%] test_api_auto.py::test_api[创建订单] PASSED [100%] 2 passed in 0.32s 5.3 一键生成并执行 JMeter 压测执行python main.py --tool jmeterJMeter 会在非 GUI 模式下运行生成的gen_scripts/api_load_test.jmx。执行完成后reports/jmeter_html目录下会生成可视化 HTML 报告可以直接用浏览器打开。命令行输出的末尾通常能看到... end of run5.4 一键生成 SQL 脚本执行python main.py --tool sql程序会读取配置中的 SQL 语句类型、表名、条件生成gen_scripts/biz_script.sql并把内容打印到控制台。你可以打开文件检查 SQL 是否符合预期再决定是否在测试库执行。5.5 一键串联三个工具如果你想一次跑完三个工具使用python main.py --all对于“准备测试数据 - 接口功能验证 - 性能压测”这条链路来说一条命令就能完成大部分重复工作。6. 常见问题与排查思路6.1 环境变量类报错“无法将 xxx 识别为 cmdlet”是 Windows 用户最常见的报错之一JMeter、pytest、npm、pnpm、Claude CLI 等工具都可能出现。问题现象常见原因解决思路jmeter无法识别JMeter 未安装或 bin 目录未加入 PATH下载 JMeter 解压后把bin目录加入 PATHpytest无法识别Python 的 Scripts 目录未加入 PATH改用python -m pytest或把 Scripts 目录加入 PATHnpm/pnpm无法识别Node.js 未安装或环境变量异常重新安装 Node.js确认安装目录已在 PATHclaude/opencode无法识别CLI 工具安装目录不在 PATH使用安装输出中的完整路径或重新配置 PATH工具箱里的check_env()会在运行前把所有缺失命令列出来帮助你提前定位问题。6.2 JMeter 模板打不开或解析失败如果生成的 JMX 文件在 JMeter GUI 中打开时提示 XML 解析错误大概率是 JMX 结构不完整或者和本机 JMeter 版本不兼容。建议先在本机 JMeter 中手动创建一个测试计划另存为模板再替换模板里的占位符。不要直接依赖网上找的任意 JMX 文件。6.3 pytest 收集不到用例生成脚本后执行 pytest 时提示collected 0 items常见原因是模板渲染后的CASES为空列表或者函数名不符合 pytest 的收集规则。检查config.yaml中cases是否为空检查模板中测试函数是否以test_开头。6.4 SQL 中文乱码生成的 SQL 文件在数据库客户端打开后中文显示乱码通常是编码不一致。工具箱写入文件时统一使用encodingutf-8你在数据库客户端打开文件时也需要选择 UTF-8 编码。如果使用 Windows 记事本打开另存时注意编码选择。6.5 安全检查误报check_dangerous()的黑名单匹配的是关键词子串比较严格。如果 SQL 中刚好包含union之类的单词被误判可以把条件写得更加精确或者把人工评审作为最终把关方式。需要注意这不是让你绕过安全校验而是理解它属于“兜底规则”。7. 最佳实践与工程建议7.1 模板资产化模板是整个工具箱最核心的资产。templates/目录下的 pytest 模板和 JMX 模板应该纳入版本管理像代码一样走变更评审。团队成员不要每个人本地维护一份模板否则会出现“我的模板能用你的模板报错”的情况。7.2 用 AI 辅助生成初稿用工具箱兜底现在大模型生成脚本初稿已经很常见。你可以让 AI 根据接口文档生成 pytest 用例再把用例整理进config.yaml的 cases 字段也可以让 AI 生成一段 JMeter HTTP Sampler 的 XML再同步到模板中。工具箱负责把模板渲染成可运行脚本并完成环境自检、命令执行、报告输出。AI 负责把经验约束到模板内容里工具箱负责让模板真正跑起来。两者结合效率提升会非常明显。7.3 生产环境的安全边界工具箱默认只负责生成脚本和执行本地测试命令不建议直接连接生产库执行 SQL。生产环境的任何 DELETE、UPDATE 都应该走变更审批流程并且满足以下条件操作前有完整备份或可回滚方案。使用最小权限账号只授权必要表、必要操作。UPDATE/DELETE 必须带 WHERE先 SELECT 确认影响行数。7.4 性能压测注意点JMeter 压测前先用 1 个线程、1 次循环验证接口连通性。正式压测时再逐步增加线程数避免一开始就把测试环境打挂。压测过程中记录环境信息、线程数、循环次数、压测时间方便和后续结果对比。如果压测结果里线程数不是配置的 50检查 JMX 模板中ThreadGroup.num_threads是否被正确替换以及 JMeter 是否有其他配置覆盖了该值。7.5 SQL 变更与审计SQL 脚本统一从配置生成改动有迹可循这是很大的优势。实际项目中建议额外关注慢 SQL 问题。生成 SELECT 语句时尽量指定列名避免SELECT *遇到慢 SQL先用EXPLAIN查看执行计划再看索引是否合理。对于大批量 UPDATE/DELETE建议分批提交避免长时间锁表。大批量 INSERT 也优先使用批量插入语法减少网络往返。8. 总结与下一步本文实现了一个“Pytest / JMeter / SQL 脚本生成与执行”的 skill 工具箱核心思路是模板驱动、配置隔离、环境自检、统一入口。pytest 模块可以根据配置批量生成接口用例并自动执行JMeter 模块可以生成 JMX 压测脚本并调用命令行压测SQL 模块可以根据配置生成测试数据脚本同时内置了 WHERE 强制校验和危险关键词拦截。下一步你可以继续学习pytest 的 fixture、hook、Allure 报告集成JMeter 的分布式压测、上传文件、HTTPS 脚本录制SQL 的窗口函数、执行计划分析、慢查询优化。把这套工具箱跑通之后建议先用一个真实项目的小接口替换示例数据完整走一遍“SQL 准备数据 - pytest 验证接口 - JMeter 压测”的流程再逐步增加自己的模板和规则。
返回列表