openapi-crud-gen
从 OpenAPI 3.x 描述文件生成 pytest + requests 的 CRUD 测试骨架。
openapi-crud-gen 是一个纯静态代码生成 CLI:它读取 OpenAPI 3.0 / 3.1 文档(YAML 或 JSON),
识别 RESTful 资源及其 CRUD 操作,为每个资源生成一个可直接被 pytest 收集的测试模块。
生成过程不发起任何 HTTP 请求,业务数据与断言细节以 TODO 形式留给使用者补齐。
适用于契约测试、测试左移、CRUD 自动化与 CI 质量门禁的起点搭建。
快速上手(60 秒)
想跟着一步步走(真实输出、为什么必须填 TODO、怎么接进 CI),看 docs/TUTORIAL.md。
pip install -e . # 1. 安装(pytest/requests 已在核心依赖里)
openapi-crud-gen examples/openapi.yaml --output-dir tests/generated # 2. 生成骨架
python examples/mock_api.py # 3. 另开一个终端:起个假后端
pytest tests/generated # 4. 看到 5 passed
第 3 步只是让你立刻看到"生成的东西真的能跑"。真实使用时跳过它,把
--base-url 指向你自己的服务,并把生成文件里的 TODO(路径参数、请求体、断言)补齐。
生成结束后 CLI 会直接打印这三步提示,不用记:
Generated 2 file(s) in tests/generated (0 skipped, 0 endpoint(s) ignored)
Next steps:
1. Fill in the TODOs in the generated files (path parameters, request payload, assertions).
2. Start your API at http://localhost:8000, or regenerate with a different --base-url.
3. Run: pytest tests/generated
常见问题
跑生成出来的测试报 ConnectionError: [WinError 10061] 由于目标计算机积极拒绝
生成的是会真实发请求的骨架,BASE_URL 上必须真的有人监听。启动你的后端,或先
python examples/mock_api.py;也可以换地址重新生成:--base-url http://127.0.0.1:9000。
requests 报 ModuleNotFoundError
requests 已经随本工具一起装好,出现这个错说明跑 pytest 的解释器不是装工具的那个。
在跑测试的环境里执行 pip install openapi-crud-gen(源码目录用 pip install .)。
为什么路径参数是 id = None
骨架不知道你的真实 ID。写成 None 是为了避免运行时 NameError,跑之前替换成真实值。
状态码断言失败:文档写 [201],接口却返回 200
断言取自文档声明的响应码。接口实现和文档不一致时,改文档或改断言,别把断言删成
assert response.status_code。
文档里声明了 404/400,为什么断言里没有
只自动断言 2xx/3xx,避免断言恒真失去意义。需要时在 # TODO: 补充响应体断言 处自己加。
只想看会生成什么、不写文件
加 --dry-run。
重新生成时提示 skipped ... (exists)
默认不覆盖已有文件,确认要覆盖就加 --overwrite;手写的断言不会被悄悄冲掉。
安装
需要 Python 3.10+。
pip install openapi-crud-gen # 工具 + pytest + requests,一条命令把环境装好
pytest 与 requests 是核心依赖(生成的测试要用它们跑),装完就能直接
pytest tests/generated,不会出现 ModuleNotFoundError: No module named 'requests'。
从源码装(仓库尚未发布到 PyPI 时)
pip install . # 在项目根目录执行
pip install -e . # 要改代码时用可编辑安装
pip install ".[dev]" # 额外装上 pytest-cov,用于跑覆盖率门槛
uv pip install -e . # 用 uv 也一样
pip install openapi-crud-gen要等包上传 PyPI 后才有效;发布准备已完成(PEP 639 元数据、twine check双 PASSED、wheel 内含模板),发布命令见下面的"发布到 PyPI"。
使用
openapi-crud-gen [OPTIONS] OPENAPI_FILE
| 选项 | 默认值 | 说明 |
|---|---|---|
--output-dir PATH |
tests/generated |
生成文件输出目录,不存在则创建 |
--base-url TEXT |
http://localhost:8000 |
写入生成文件 BASE_URL 常量的值 |
--resource-filter TEXT |
无 | 逗号分隔资源名,仅生成匹配资源;未匹配项只告警 |
--overwrite |
关闭 | 覆盖已存在文件;关闭时跳过并提示 |
--dry-run |
关闭 | 只打印将要生成的文件,不写盘 |
--version |
— | 打印版本号并退出 |
退出码:0 成功(含“无可生成资源”);1 文件无法解析或不是 OpenAPI 3.x;
2 参数错误(如文件不存在)。OpenAPI schema 校验失败只输出警告,不影响生成。
示例输入
examples/openapi.yaml:
openapi: 3.0.3
info:
title: User API
version: 1.0.0
servers:
- url: http://localhost:8000
paths:
/users:
get:
summary: 获取用户列表
responses:
'200':
description: OK
post:
summary: 创建用户
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
name:
type: string
responses:
'201':
description: Created
/users/{id}:
get:
summary: 获取用户详情
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: OK
put:
summary: 更新用户
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'200':
description: OK
delete:
summary: 删除用户
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
'204':
description: No Content
运行
openapi-crud-gen examples/openapi.yaml --output-dir tests/generated --base-url http://localhost:8000
Loaded examples/openapi.yaml (OpenAPI 3.0.3)
Recognized CRUD resources
+-----------------------------------------------------------------------+
| Resource | Base path | Actions |
|----------+-----------+------------------------------------------------|
| users | /users | create, read_list, read_detail, update, delete |
+-----------------------------------------------------------------------+
Generated 2 file(s) in tests/generated (0 skipped, 0 endpoint(s) ignored)
生成结果
tests/generated/
├── __init__.py # 输出目录缺少时自动生成
└── test_users_crud.py # 每个资源一个文件
tests/generated/test_users_crud.py:
"""CRUD test skeleton for the users resource.
Generated by openapi-crud-gen from an OpenAPI document. Fill in every TODO
before running these tests against a live service.
"""
import pytest
import requests
BASE_URL = "http://localhost:8000"
def test_users_create():
# TODO: 准备请求数据
response = requests.post(f"{BASE_URL}/users", json={})
assert response.status_code in [201]
# TODO: 补充响应体断言
def test_users_read_list():
response = requests.get(f"{BASE_URL}/users")
assert response.status_code in [200]
# TODO: 补充响应体断言
def test_users_read_detail():
id = None # TODO: 替换路径参数
response = requests.get(f"{BASE_URL}/users/{id}")
assert response.status_code in [200]
# TODO: 补充响应体断言
def test_users_update():
id = None # TODO: 替换路径参数
# TODO: 准备请求数据
response = requests.put(f"{BASE_URL}/users/{id}", json={})
assert response.status_code in [200]
# TODO: 补充响应体断言
def test_users_delete():
id = None # TODO: 替换路径参数
response = requests.delete(f"{BASE_URL}/users/{id}")
assert response.status_code in [204]
# TODO: 补充响应体断言
生成的文件可以立即被 pytest 收集:
pytest --collect-only tests/generated
CRUD 识别规则
资源名取路径的第一段:/users/{id} → users。路径形态只有两种参与识别:
| 形态 | 判定 | 例 |
|---|---|---|
| Collection | 仅一个非参数段 | /users |
| Detail | 一个非参数段 + 恰好一个路径参数段 | /users/{id} |
| 动作 key | 方法 + 形态 | 默认状态码(文档未声明 2xx/3xx 时) |
|---|---|---|
create |
POST + Collection |
201 |
read_list |
GET + Collection |
200 |
read_detail |
GET + Detail |
200 |
update |
PUT 或 PATCH + Detail |
200 |
delete |
DELETE + Detail |
204 |
补充约定:
- 状态码取自文档中该操作声明的
200–399码,升序去重;4xx/5xx 不写入断言(避免断言恒真)。 PUT与PATCH同时存在时PUT优先,另一个计入ignored。POST/PUT/PATCH(或文档声明了requestBody的操作)生成json={}占位。- 非 CRUD 形态(
/users/{id}/posts、/users/me、/{tenant}/users、根路径)以及HEAD/OPTIONS/TRACE不生成用例,只在摘要中计入ignored。 - 资源名规范化为合法标识符:
user-profiles→user_profiles,2fa→r_2fa,class→class_。
Python API
from openapi_crud_gen.crud import identify_crud
from openapi_crud_gen.generator import generate_tests
from openapi_crud_gen.parser import extract_endpoints, load_openapi, validate_spec
spec = load_openapi("openapi.yaml")
warnings = validate_spec(spec) # 只返回消息,不抛异常
resources = identify_crud(extract_endpoints(spec))
files = generate_tests(resources, "tests/generated", "http://localhost:8000")
限制与已知问题
- 只生成骨架:不执行真实请求,不生成认证、数据准备、数据清理逻辑。
- 只识别一级扁平资源,不支持嵌套/子资源与自定义动作(如
POST /users/{id}/activate)。 - 生成的用例相互独立,
create返回的 id 不会传给read_detail/update/delete。 $ref参数与components复用不会展开,请求体只生成{}占位。--base-url只取命令行值,不读取文档servers(保持与默认值行为一致)。- 不支持 Swagger 2.0;
servers变量模板不解析。 - 资源名规范化可能把两个不同路径段折叠为同名资源(
/user-profiles与/user.profiles)。
开发指南
pip install -e ".[dev]"
pytest # 跑测试
pytest --cov=openapi_crud_gen --cov-fail-under=80 # 加上覆盖率门槛(CI 用这条)
- 结构:
parser.py(加载与展平)→crud.py(识别资源与动作)→generator.py+templates/test_crud.py.j2(渲染)→cli.py(命令行与摘要)。 - 测试:
tests/fixtures/提供 YAML 与 JSON 示例文档;CLI 测试使用click.testing.CliRunner; 测试不发起任何网络请求。 - 覆盖率门槛 80%(分支覆盖)由显式命令与 CI 强制;普通
pytest不注入--cov, 这样按快速上手直接pytest tests/generated不会被覆盖率门槛误伤。 - CI:
.github/workflows/test.yml在 push / PR 上运行 Python 3.10、3.11、3.12 矩阵, 包含全量测试与一次 CLI 冒烟(生成 +pytest --collect-only)。
发布到 PyPI(可选)
python -m pip install build twine
python -m build # 产物在 dist/:wheel + sdist
python -m twine check dist/* # 两个都要 PASSED
python -m twine upload dist/* # 需要 PyPI API token
pip install openapi-crud-gen # 发布后从 PyPI 复验一次
发布前清单:
-
pyproject.toml、src/openapi_crud_gen/__init__.py、CHANGELOG.md三处版本号一致 -
pytest --cov=openapi_crud_gen --cov-fail-under=80通过 -
twine check dist/*全 PASSED - 补
[project.urls](仓库 / Issues 地址)与作者信息,目前刻意留空以免写错地址 - 打 tag:
git tag v0.1.0 && git push origin v0.1.0
License
MIT,见 LICENSE。
Metadata
Release files for openapi-crud-gen 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| openapi_crud_gen-0.1.0.tar.gz | 33.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| openapi_crud_gen-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 49.1 kB
Release files / openapi_crud_gen-0.1.0.tar.gz
| Download URL | openapi_crud_gen-0.1.0.tar.gz |
|---|---|
| Size | 33.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
cf4464e931650877dfcafa4ea49584c1cdafac369b6675021fc6be2687b316f1
|
|
BLAKE2b-256 checksum How to use checksums |
796063e198180eb34117ff102cd39aac916714591ba28849a76df38cf957d019
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|
Release files / openapi_crud_gen-0.1.0-py3-none-any.whl
| Download URL | openapi_crud_gen-0.1.0-py3-none-any.whl |
|---|---|
| Size | 15.8 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
21418bb87b04bc5152123f981dd96ae09f534c1a4a4fb6348184605e6867c2ec
|
|
BLAKE2b-256 checksum How to use checksums |
ce39959926433959609a365c2d9e0fba5782a7c9b3fb6b2d224d1e1905a7c657
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.14.7
|