2052 lines
55 KiB
Markdown
2052 lines
55 KiB
Markdown
# pytest + Jenkins 自动化执行接入后端详细方案(MVC / 可直接交付 AI 编码)
|
||
|
||
## 1. 目标与边界
|
||
|
||
### 1.1 目标
|
||
基于当前项目 `D:\zhyy\effekt-interface` 的现有 MVC 结构,为“计划中的自动化用例执行”设计一套可直接落地的后端方案,支持:
|
||
|
||
1. 单条功能用例触发自动化执行。
|
||
2. 测试计划维度批量触发自动化执行。
|
||
3. 自动化项目基于 `pytest` 执行指定 `case_id` 对应的用例。
|
||
4. Jenkins 作为统一调度入口。
|
||
5. 平台记录执行任务、执行明细、状态流转、Jenkins 构建信息、回调结果。
|
||
6. 方案需适配当前项目的 MVC 分层:
|
||
- controller: `app/api/controller/*.py`
|
||
- service: `app/api/service/*.py`
|
||
- dao: `app/api/dao/*.py`
|
||
- model: `app/api/model/*.py`
|
||
- route: `app/api/views.py`
|
||
|
||
### 1.2 已知现状
|
||
经代码结构确认:
|
||
|
||
- 当前项目是 Flask + SQLAlchemy Session 的轻量 MVC。
|
||
- 已有测试计划、计划用例、测试用例表:
|
||
- `test_plan`
|
||
- `plan_case`
|
||
- `test_case`
|
||
- 当前计划执行 `plan_case_execute` 仅支持手工回填结果,不支持自动化编排。
|
||
- 功能用例与自动化用例的桥梁是 `case_id`。
|
||
- `test_case.is_auto` 已可区分是否自动化。
|
||
- 当前 controller/service/dao 的风格是:
|
||
- controller 做参数校验和聚合返回
|
||
- service 主要转发 dao 或封装少量业务
|
||
- dao 负责数据库 CRUD
|
||
- 当前项目已有 HTTP 请求封装:`common/getRequest.py -> Request.go(...)`
|
||
- 当前有 `common/cronRequest.py` 作为外部系统请求示例,但不适合直接复用为 Jenkins 客户端,建议新增独立 Jenkins 请求封装。
|
||
|
||
### 1.3 本期边界
|
||
只设计后端逻辑,不做前端实现,不写自动化项目代码,但会定义自动化项目与平台的接口契约。
|
||
|
||
---
|
||
|
||
## 2. 总体架构设计
|
||
|
||
推荐采用:
|
||
|
||
**平台编排 + Jenkins 参数化触发 + pytest 项目按 execution_id 拉取执行清单 + 回调平台写回结果**
|
||
|
||
### 2.1 责任划分
|
||
|
||
#### 平台(本项目)
|
||
负责:
|
||
- 识别单条/计划执行请求
|
||
- 校验用例是否可自动化执行
|
||
- 创建执行记录
|
||
- 调用 Jenkins Job
|
||
- 提供执行清单查询接口给 pytest 项目拉取
|
||
- 接收执行中/执行完成回调
|
||
- 聚合执行结果并更新计划状态
|
||
|
||
#### Jenkins
|
||
负责:
|
||
- 接收平台触发请求
|
||
- 以参数化方式启动自动化项目
|
||
- 记录构建号、日志、报告地址
|
||
- 失败兜底回调平台
|
||
|
||
#### pytest 自动化项目
|
||
负责:
|
||
- 接收 Jenkins 参数
|
||
- 通过 `execution_id` 调平台接口获取待执行 case 列表
|
||
- 按 `case_id` 映射执行 pytest 用例
|
||
- 回调平台单条结果和最终结果
|
||
- 输出 junit/allure/json 等结果
|
||
|
||
### 2.2 为什么不直接把 case_id 列表塞进 Jenkins 参数
|
||
不推荐直接传长列表,理由:
|
||
- 计划内用例多时参数长度不可控。
|
||
- URL/参数转义复杂。
|
||
- 后续扩展环境、标签、重跑等字段时耦合变高。
|
||
- 执行审计不清晰。
|
||
|
||
推荐只传 `execution_id`,由 pytest 项目回平台拉取清单。
|
||
|
||
---
|
||
|
||
## 3. 与当前 MVC 框架的集成方式
|
||
|
||
### 3.1 新增模块建议
|
||
在现有 `case / plan / report` 域外,新增一个独立“自动化执行域”:
|
||
|
||
- controller: `app/api/controller/automationController.py`
|
||
- service: `app/api/service/automationService.py`
|
||
- dao: `app/api/dao/automationDao.py`
|
||
- model: `app/api/model/automationModel.py`
|
||
- util/client: `common/jenkinsRequest.py`
|
||
|
||
### 3.2 为什么独立成 automation 域
|
||
不要把自动化执行逻辑继续塞进 `planController.py` 或 `caseController.py`,原因:
|
||
- 自动化执行有独立生命周期,不只是计划或用例的附属字段。
|
||
- 涉及外部系统 Jenkins 调用、结果回写、状态机、执行明细聚合。
|
||
- 拆成独立域后,controller/service/dao 责任更清晰。
|
||
|
||
### 3.3 与现有域的关系
|
||
|
||
#### 与 Case 域的关系
|
||
- `test_case.id` 作为桥梁。
|
||
- 通过 `test_case.is_auto=1` 判断可自动化。
|
||
- 后续如需补充自动化元信息,可扩展 `test_case` 字段或新增自动化映射表。
|
||
|
||
#### 与 Plan 域的关系
|
||
- 批量执行来源于 `plan_case`。
|
||
- 自动化执行结果需要同步回写 `plan_case.status / actual_result / executed_time / execution_duration`。
|
||
- 自动化执行完成后,要复用或增强当前 `_update_plan_status` 的逻辑,更新 `test_plan.status`。
|
||
|
||
---
|
||
|
||
## 4. 数据库表结构设计
|
||
|
||
本方案采用:
|
||
- 尽量复用现有 `test_case / plan_case / test_plan`
|
||
- 新增执行主表和执行明细表
|
||
- 可选新增执行事件表(如要完整审计)
|
||
|
||
---
|
||
|
||
## 5. 现有表改造建议
|
||
|
||
### 5.1 `test_case` 表改造
|
||
当前已有:
|
||
- `id`
|
||
- `case_key`
|
||
- `is_auto`
|
||
|
||
由于你说明桥梁就是 `case_id`,pytest 项目也按 `case_id` 执行,因此**本期不强制新增自动化用例定位字段**。
|
||
|
||
但建议增加以下可选字段,便于后续扩展:
|
||
|
||
```sql
|
||
ALTER TABLE test_case
|
||
ADD COLUMN auto_status SMALLINT DEFAULT 0,
|
||
ADD COLUMN auto_updated_time TIMESTAMP,
|
||
ADD COLUMN auto_meta JSONB DEFAULT '{}'::jsonb;
|
||
```
|
||
|
||
字段说明:
|
||
- `auto_status`: 0未接入 1已接入 2已失效
|
||
- `auto_updated_time`: 自动化信息最近更新时间
|
||
- `auto_meta`: 预留字段,后续可存 pytest marker、目录、owner 等
|
||
|
||
> 如果你要严格最小改动,这 3 个字段可以不做。
|
||
|
||
---
|
||
|
||
## 6. 新增核心表结构
|
||
|
||
### 6.1 自动化执行主表 `auto_execution`
|
||
|
||
用途:记录一次自动化执行任务,支持单条执行和计划执行。
|
||
|
||
```sql
|
||
CREATE TABLE auto_execution (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
execution_no VARCHAR(64) NOT NULL UNIQUE,
|
||
trigger_type SMALLINT NOT NULL,
|
||
project_id BIGINT NOT NULL,
|
||
plan_id BIGINT,
|
||
plan_round_no INTEGER,
|
||
source_case_id BIGINT,
|
||
env_code VARCHAR(32) NOT NULL,
|
||
run_mode SMALLINT DEFAULT 1,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
jenkins_job_name VARCHAR(128),
|
||
jenkins_queue_id BIGINT,
|
||
jenkins_build_number BIGINT,
|
||
jenkins_build_url VARCHAR(512),
|
||
console_url VARCHAR(512),
|
||
report_url VARCHAR(512),
|
||
total_count INTEGER DEFAULT 0,
|
||
pending_count INTEGER DEFAULT 0,
|
||
running_count INTEGER DEFAULT 0,
|
||
passed_count INTEGER DEFAULT 0,
|
||
failed_count INTEGER DEFAULT 0,
|
||
blocked_count INTEGER DEFAULT 0,
|
||
skipped_count INTEGER DEFAULT 0,
|
||
not_found_count INTEGER DEFAULT 0,
|
||
trigger_by BIGINT,
|
||
trigger_source VARCHAR(32) DEFAULT 'platform',
|
||
trigger_message TEXT,
|
||
start_time TIMESTAMP,
|
||
end_time TIMESTAMP,
|
||
duration_seconds INTEGER,
|
||
callback_token VARCHAR(128),
|
||
ext JSONB DEFAULT '{}'::jsonb,
|
||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
字段定义:
|
||
|
||
- `execution_no`: 平台执行编号,建议格式 `AE202502120001`
|
||
- `trigger_type`:
|
||
- 1 单条用例执行
|
||
- 2 计划批量执行
|
||
- `project_id`: 冗余存项目,方便查询
|
||
- `plan_id`: 计划执行时必填,单条执行可空
|
||
- `plan_round_no`: 若按轮次执行可记录
|
||
- `source_case_id`: 单条执行来源 case_id
|
||
- `env_code`: 执行环境编码,如 `st/dev/pre`
|
||
- `run_mode`:
|
||
- 1 串行
|
||
- 2 并行
|
||
- `status`: 执行主状态,见后文状态机
|
||
- `jenkins_job_name / queue_id / build_number / build_url`
|
||
- `console_url / report_url`: 用于前端直接跳转
|
||
- `*_count`: 执行汇总
|
||
- `trigger_by`: 触发用户
|
||
- `trigger_message`: 触发失败或备注
|
||
- `callback_token`: Jenkins/pytest 回调鉴权
|
||
- `ext`: 预留字段,记录 branch、pytest args、report meta 等
|
||
|
||
索引建议:
|
||
|
||
```sql
|
||
CREATE INDEX idx_auto_execution_plan_id ON auto_execution(plan_id);
|
||
CREATE INDEX idx_auto_execution_project_id ON auto_execution(project_id);
|
||
CREATE INDEX idx_auto_execution_status ON auto_execution(status);
|
||
CREATE INDEX idx_auto_execution_created_time ON auto_execution(created_time DESC);
|
||
CREATE INDEX idx_auto_execution_trigger_by ON auto_execution(trigger_by);
|
||
```
|
||
|
||
---
|
||
|
||
### 6.2 自动化执行明细表 `auto_execution_case`
|
||
|
||
用途:记录本次执行下每个 `case_id` 的状态和结果。
|
||
|
||
```sql
|
||
CREATE TABLE auto_execution_case (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
execution_id BIGINT NOT NULL,
|
||
plan_case_id BIGINT,
|
||
case_id BIGINT NOT NULL,
|
||
case_key VARCHAR(64),
|
||
case_title VARCHAR(255),
|
||
run_order INTEGER DEFAULT 0,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
pytest_nodeid VARCHAR(512),
|
||
result_message TEXT,
|
||
error_message TEXT,
|
||
stack_trace TEXT,
|
||
report_url VARCHAR(512),
|
||
duration_seconds INTEGER,
|
||
started_time TIMESTAMP,
|
||
finished_time TIMESTAMP,
|
||
retry_count INTEGER DEFAULT 0,
|
||
ext JSONB DEFAULT '{}'::jsonb,
|
||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
字段定义:
|
||
- `execution_id`: 对应 `auto_execution.id`
|
||
- `plan_case_id`: 如果来源于计划执行,则关联 `plan_case.id`
|
||
- `case_id`: 功能用例主键,也是自动化桥梁
|
||
- `case_key / case_title`: 冗余快照,避免后续名称变化影响历史展示
|
||
- `run_order`: 执行顺序
|
||
- `status`: 单用例执行状态,见后文状态机
|
||
- `pytest_nodeid`: pytest 的 nodeid,如 `tests/test_login.py::test_xxx`
|
||
- `result_message`: 简要结果,如断言失败摘要
|
||
- `error_message / stack_trace`: 异常信息
|
||
- `report_url`: 若支持单用例报告可记录
|
||
- `retry_count`: 失败重跑预留
|
||
- `ext`: 可存截图、附件、标签等
|
||
|
||
索引建议:
|
||
|
||
```sql
|
||
CREATE INDEX idx_auto_execution_case_execution_id ON auto_execution_case(execution_id);
|
||
CREATE INDEX idx_auto_execution_case_case_id ON auto_execution_case(case_id);
|
||
CREATE INDEX idx_auto_execution_case_plan_case_id ON auto_execution_case(plan_case_id);
|
||
CREATE INDEX idx_auto_execution_case_status ON auto_execution_case(status);
|
||
CREATE UNIQUE INDEX uk_auto_execution_case_unique ON auto_execution_case(execution_id, case_id, plan_case_id);
|
||
```
|
||
|
||
> 如果同一计划允许相同 `case_id` 在不同轮次、多次加入,则唯一索引可调整成 `(execution_id, plan_case_id, case_id)`,其中 `plan_case_id` 允许为空时要注意 PostgreSQL 对 NULL 唯一性的行为。
|
||
|
||
---
|
||
|
||
### 6.3 可选事件表 `auto_execution_event`
|
||
|
||
如果你希望保留每次回调、状态变化、Jenkins 交互轨迹,建议加事件表。
|
||
|
||
```sql
|
||
CREATE TABLE auto_execution_event (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
execution_id BIGINT NOT NULL,
|
||
execution_case_id BIGINT,
|
||
event_type VARCHAR(64) NOT NULL,
|
||
event_status VARCHAR(32),
|
||
payload JSONB DEFAULT '{}'::jsonb,
|
||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
事件类型示例:
|
||
- `TRIGGER_REQUESTED`
|
||
- `JENKINS_QUEUED`
|
||
- `JENKINS_STARTED`
|
||
- `CASE_STARTED`
|
||
- `CASE_FINISHED`
|
||
- `EXECUTION_FINISHED`
|
||
- `CALLBACK_FAILED`
|
||
|
||
> 若第一期追求最小化,可先不建此表,把关键日志写到应用日志中。
|
||
|
||
---
|
||
|
||
## 7. SQLAlchemy Model 设计(贴合当前项目风格)
|
||
|
||
建议新增文件:`app/api/model/automationModel.py`
|
||
|
||
包含:
|
||
- `AutoExecution`
|
||
- `AutoExecutionCase`
|
||
- (可选)`AutoExecutionEvent`
|
||
|
||
建模风格应与当前 `planModel.py / caseModel.py` 一致:
|
||
- 使用 `declarative_base()`
|
||
- `Base.to_dict = to_dict`
|
||
- 字段命名与数据库下划线保持一致
|
||
- `JSONB / TIMESTAMP / SmallInteger / BigInteger`
|
||
|
||
---
|
||
|
||
## 8. 状态流转设计
|
||
|
||
这是后端实现的核心。
|
||
|
||
### 8.1 主执行状态 `auto_execution.status`
|
||
|
||
建议枚举:
|
||
|
||
- `0` 待触发(平台已建单,尚未请求 Jenkins)
|
||
- `1` 触发中(正在调用 Jenkins)
|
||
- `2` 排队中(Jenkins 已接收,尚未开始构建)
|
||
- `3` 执行中(Jenkins/pytest 已开始)
|
||
- `4` 成功(全部执行完成且失败数=0)
|
||
- `5` 失败(已结束,但存在失败/异常)
|
||
- `6` 已取消
|
||
- `7` 触发失败(Jenkins API 调用失败)
|
||
- `8` 回调异常(任务可能跑完,但平台未完整接收结果)
|
||
|
||
推荐状态流:
|
||
|
||
```text
|
||
0待触发
|
||
-> 1触发中
|
||
-> 2排队中
|
||
-> 3执行中
|
||
-> 4成功
|
||
-> 5失败
|
||
-> 6已取消
|
||
-> 7触发失败
|
||
```
|
||
|
||
补偿状态:
|
||
- 任意执行中状态,如果最终结果缺失但监控发现 Jenkins 已完成,可修正到 4/5。
|
||
- 回调鉴权失败/数据异常时,可标记 `8 回调异常`。
|
||
|
||
### 8.2 明细状态 `auto_execution_case.status`
|
||
|
||
建议枚举:
|
||
- `0` 待执行
|
||
- `1` 执行中
|
||
- `2` 通过
|
||
- `3` 失败
|
||
- `4` 阻塞
|
||
- `5` 跳过
|
||
- `6` 未找到自动化实现
|
||
- `7` 已取消
|
||
|
||
推荐状态流:
|
||
|
||
```text
|
||
0待执行 -> 1执行中 -> 2通过
|
||
-> 3失败
|
||
-> 4阻塞
|
||
-> 5跳过
|
||
-> 6未找到自动化实现
|
||
-> 7已取消
|
||
```
|
||
|
||
### 8.3 与现有 `plan_case.status` 的映射关系
|
||
|
||
当前 `plan_case.status`:
|
||
- `0` 未开始
|
||
- `1` 通过
|
||
- `2` 失败
|
||
- `3` 阻塞
|
||
|
||
自动化结果回写时建议映射:
|
||
|
||
- `auto_execution_case.status=2` -> `plan_case.status=1`
|
||
- `auto_execution_case.status=3` -> `plan_case.status=2`
|
||
- `auto_execution_case.status=4` -> `plan_case.status=3`
|
||
- `auto_execution_case.status in (5,6,7)` -> **不自动覆盖为通过/失败**,建议保留原值或按业务约定:
|
||
- 若原来是 `0`,可保持 `0`
|
||
- 并把细节记入 `actual_result`
|
||
|
||
### 8.4 与 `test_plan.status` 的映射关系
|
||
|
||
当前 `TestPlan.status`:
|
||
- `0` 草稿
|
||
- `1` 进行中
|
||
- `2` 已完成
|
||
- `3` 已归档
|
||
- `4` 已通过
|
||
|
||
建议复用现有 `_update_plan_status(plan_id)` 逻辑,但将其抽到 `PlanService` 或 `AutomationService` 中统一调用。
|
||
|
||
执行过程中:
|
||
- 计划中存在未执行 -> `1 进行中`
|
||
- 全部完成,且无失败/阻塞 -> `4 已通过`
|
||
- 全部完成,存在失败/阻塞 -> `2 已完成`
|
||
- 若原来 `3 已归档`,不自动改动
|
||
|
||
---
|
||
|
||
## 9. 接口设计
|
||
|
||
接口分三类:
|
||
1. 前端触发接口
|
||
2. 自动化项目拉取执行清单接口
|
||
3. Jenkins/pytest 回调接口
|
||
|
||
以下设计遵循当前项目风格:
|
||
- 路由统一注册在 `app/api/views.py`
|
||
- `GET` 查列表/详情
|
||
- `POST` 创建/更新/触发
|
||
- 返回结构统一 `ApiResponse.build_success/build_failure`
|
||
|
||
---
|
||
|
||
## 10. 前端触发接口设计
|
||
|
||
### 10.1 单条用例执行
|
||
|
||
#### 路由
|
||
`POST /api/automation/case/run`
|
||
|
||
#### 权限
|
||
建议新增:`automation:run`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"caseId": 1001,
|
||
"envCode": "st",
|
||
"runMode": 1,
|
||
"jenkinsJobName": "pytest-auto-runner",
|
||
"remark": "单条回归验证"
|
||
}
|
||
```
|
||
|
||
#### 字段说明
|
||
- `caseId`: 必填
|
||
- `envCode`: 必填
|
||
- `runMode`: 可选,默认 1 串行
|
||
- `jenkinsJobName`: 可选,未传则走系统默认配置
|
||
- `remark`: 可选
|
||
|
||
#### 返回
|
||
```json
|
||
{
|
||
"id": 123,
|
||
"executionId": 123,
|
||
"executionNo": "AE202502120001",
|
||
"status": 2
|
||
}
|
||
```
|
||
|
||
#### 处理逻辑
|
||
1. 校验 `caseId` 是否存在。
|
||
2. 校验 `test_case.is_auto == 1`。
|
||
3. 创建 `auto_execution`:
|
||
- `trigger_type=1`
|
||
- `source_case_id=caseId`
|
||
- `project_id` 从 `test_case.project_id` 带出
|
||
4. 创建一条 `auto_execution_case`。
|
||
5. 调用 Jenkins。
|
||
6. 若调用成功:
|
||
- 更新主表 `status=2`
|
||
- 记录 `queue_id / job_name`
|
||
7. 若调用失败:
|
||
- 更新主表 `status=7`
|
||
- 返回失败原因
|
||
|
||
---
|
||
|
||
### 10.2 计划自动化执行
|
||
|
||
#### 路由
|
||
`POST /api/automation/plan/run`
|
||
|
||
#### 权限
|
||
`automation:run`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"planId": 2001,
|
||
"roundNo": 1,
|
||
"envCode": "st",
|
||
"runMode": 1,
|
||
"caseIds": [1001, 1002, 1003],
|
||
"jenkinsJobName": "pytest-auto-runner",
|
||
"remark": "计划批量回归"
|
||
}
|
||
```
|
||
|
||
#### 说明
|
||
- `caseIds` 可选:
|
||
- 不传或空:执行当前计划下全部自动化用例
|
||
- 传值:执行计划中指定子集
|
||
- `roundNo` 可选:如果你已有轮次概念,则仅执行该轮次下的 `plan_case`
|
||
|
||
#### 返回
|
||
```json
|
||
{
|
||
"id": 124,
|
||
"executionId": 124,
|
||
"executionNo": "AE202502120002",
|
||
"totalCount": 15,
|
||
"status": 2
|
||
}
|
||
```
|
||
|
||
#### 处理逻辑
|
||
1. 校验 `planId`。
|
||
2. 查询 `plan_case + test_case`:
|
||
- 必须属于该计划
|
||
- `test_case.is_auto=1`
|
||
- 若传 `caseIds`,则限制子集
|
||
- 若传 `roundNo`,则限制轮次
|
||
3. 若无可执行自动化用例,直接报错。
|
||
4. 创建 `auto_execution`:
|
||
- `trigger_type=2`
|
||
- `plan_id=planId`
|
||
- `plan_round_no=roundNo`
|
||
5. 批量创建 `auto_execution_case`。
|
||
6. 调 Jenkins。
|
||
7. 更新状态与 Jenkins 信息。
|
||
|
||
---
|
||
|
||
### 10.3 自动化执行列表
|
||
|
||
#### 路由
|
||
`GET /api/automation/execution/list`
|
||
|
||
#### 请求参数
|
||
- `projectId`
|
||
- `planId`
|
||
- `status`
|
||
- `triggerType`
|
||
- `pageNo`
|
||
- `pageSize`
|
||
|
||
#### 返回字段
|
||
- `id`
|
||
- `execution_no`
|
||
- `trigger_type`
|
||
- `plan_id`
|
||
- `source_case_id`
|
||
- `env_code`
|
||
- `status`
|
||
- `total_count`
|
||
- `passed_count`
|
||
- `failed_count`
|
||
- `report_url`
|
||
- `jenkins_build_number`
|
||
- `created_time`
|
||
- `start_time`
|
||
- `end_time`
|
||
|
||
---
|
||
|
||
### 10.4 自动化执行详情
|
||
|
||
#### 路由
|
||
`GET /api/automation/execution/detail`
|
||
|
||
#### 请求参数
|
||
- `executionId`
|
||
|
||
#### 返回
|
||
主表 + 汇总 + Jenkins 信息 + 用例统计。
|
||
|
||
---
|
||
|
||
### 10.5 自动化执行明细列表
|
||
|
||
#### 路由
|
||
`GET /api/automation/execution/case/list`
|
||
|
||
#### 请求参数
|
||
- `executionId`
|
||
- `status` 可选
|
||
- `pageNo`
|
||
- `pageSize`
|
||
|
||
#### 返回
|
||
每条 `auto_execution_case` 的详情。
|
||
|
||
---
|
||
|
||
## 11. 自动化项目拉取执行清单接口
|
||
|
||
这是 pytest 项目的核心入口。
|
||
|
||
### 11.1 获取执行清单
|
||
|
||
#### 路由
|
||
`GET /api/automation/execution/case/pull`
|
||
|
||
#### 鉴权
|
||
建议两层:
|
||
1. 正常 token 鉴权(如果自动化项目能拿到平台 token)
|
||
2. 或使用 `executionId + callbackToken` 鉴权
|
||
|
||
建议本期采用:
|
||
- query: `executionId`
|
||
- header: `X-CALLBACK-TOKEN`
|
||
|
||
#### 请求参数
|
||
- `executionId`
|
||
|
||
#### 请求头
|
||
- `X-CALLBACK-TOKEN: xxx`
|
||
|
||
#### 返回
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"executionNo": "AE202502120002",
|
||
"triggerType": 2,
|
||
"projectId": 88,
|
||
"planId": 2001,
|
||
"envCode": "st",
|
||
"runMode": 1,
|
||
"items": [
|
||
{
|
||
"executionCaseId": 501,
|
||
"planCaseId": 9001,
|
||
"caseId": 1001,
|
||
"caseKey": "TC-ZHYY-001",
|
||
"caseTitle": "登录成功",
|
||
"runOrder": 1
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 处理逻辑
|
||
1. 校验 `executionId`。
|
||
2. 校验回调 token。
|
||
3. 查询 `auto_execution` 和 `auto_execution_case`。
|
||
4. 返回待执行清单。
|
||
|
||
> 如果未来 pytest 项目还需要额外上下文,如环境 URL、测试账号、marker,也可以从 `ext` 中一起返回。
|
||
|
||
---
|
||
|
||
## 12. 回调接口设计
|
||
|
||
推荐分 4 个回调点:
|
||
1. Jenkins 入队成功
|
||
2. Jenkins 开始执行
|
||
3. 单条用例结果回写
|
||
4. 整体执行完成回写
|
||
|
||
---
|
||
|
||
### 12.1 Jenkins 入队回写
|
||
|
||
#### 路由
|
||
`POST /api/automation/execution/queued`
|
||
|
||
#### 作用
|
||
Jenkins 已接受构建,回传 queue/build 基本信息。
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"queueId": 5678,
|
||
"jobName": "pytest-auto-runner",
|
||
"buildNumber": null,
|
||
"buildUrl": null
|
||
}
|
||
```
|
||
|
||
#### 处理
|
||
- 主表状态 `1/2 -> 2`
|
||
- 回写 `queue_id / job_name / build_number / build_url`
|
||
|
||
> 如果平台触发 Jenkins 后拿不到 queueId,也可省略这个接口,由后续 started 回调补齐。
|
||
|
||
---
|
||
|
||
### 12.2 Jenkins/pytest 开始执行回写
|
||
|
||
#### 路由
|
||
`POST /api/automation/execution/start`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"jobName": "pytest-auto-runner",
|
||
"buildNumber": 89,
|
||
"buildUrl": "http://jenkins/job/pytest-auto-runner/89/",
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console",
|
||
"startTime": "2025-02-12 10:00:00"
|
||
}
|
||
```
|
||
|
||
#### 处理
|
||
- 主表状态更新为 `3 执行中`
|
||
- 记录 `buildNumber / buildUrl / consoleUrl / start_time`
|
||
|
||
---
|
||
|
||
### 12.3 单条用例结果回写
|
||
|
||
#### 路由
|
||
`POST /api/automation/execution/case/result`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"caseId": 1001,
|
||
"executionCaseId": 501,
|
||
"status": 2,
|
||
"pytestNodeid": "tests/test_login.py::test_login_success",
|
||
"resultMessage": "assert success",
|
||
"errorMessage": "",
|
||
"stackTrace": "",
|
||
"durationSeconds": 12,
|
||
"reportUrl": "http://allure/case/1001",
|
||
"startedTime": "2025-02-12 10:01:00",
|
||
"finishedTime": "2025-02-12 10:01:12",
|
||
"ext": {
|
||
"attachments": []
|
||
}
|
||
}
|
||
```
|
||
|
||
#### 处理逻辑
|
||
1. 按 `executionCaseId` 优先定位明细;若无则退化为 `executionId + caseId`。
|
||
2. 更新 `auto_execution_case`。
|
||
3. 若有关联 `plan_case_id`,同步回写 `plan_case`:
|
||
- `status`
|
||
- `actual_result`
|
||
- `executed_time`
|
||
- `execution_duration`
|
||
4. 重新聚合更新 `auto_execution` 汇总字段。
|
||
5. 若是计划执行,可调用计划状态更新逻辑。
|
||
|
||
---
|
||
|
||
### 12.4 整体执行完成回写
|
||
|
||
#### 路由
|
||
`POST /api/automation/execution/finish`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"status": 4,
|
||
"buildNumber": 89,
|
||
"buildUrl": "http://jenkins/job/pytest-auto-runner/89/",
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console",
|
||
"reportUrl": "http://allure/report/89",
|
||
"startTime": "2025-02-12 10:00:00",
|
||
"endTime": "2025-02-12 10:08:00",
|
||
"durationSeconds": 480,
|
||
"summary": {
|
||
"total": 15,
|
||
"passed": 12,
|
||
"failed": 2,
|
||
"blocked": 0,
|
||
"skipped": 1,
|
||
"notFound": 0
|
||
},
|
||
"message": "执行完成"
|
||
}
|
||
```
|
||
|
||
#### 处理逻辑
|
||
1. 更新主表 Jenkins 信息与时间。
|
||
2. 若请求体有 summary,则直接写回;否则后台实时聚合明细表得到。
|
||
3. 根据汇总决定主状态:
|
||
- `failed > 0 or blocked > 0 or notFound > 0` -> `5失败`
|
||
- 否则 -> `4成功`
|
||
4. 若是计划执行,调用计划状态刷新。
|
||
|
||
---
|
||
|
||
### 12.5 失败/取消回调
|
||
|
||
#### 路由
|
||
`POST /api/automation/execution/abort`
|
||
|
||
#### 请求体
|
||
```json
|
||
{
|
||
"executionId": 124,
|
||
"status": 6,
|
||
"message": "Jenkins aborted",
|
||
"buildNumber": 89,
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console"
|
||
}
|
||
```
|
||
|
||
#### 处理
|
||
- 主表置 `6 已取消` 或 `5 失败`
|
||
- 所有仍为 `0/1` 的明细置为 `7 已取消`
|
||
- 刷新汇总
|
||
|
||
---
|
||
|
||
## 13. Jenkins 参数设计
|
||
|
||
推荐只使用少量稳定参数。
|
||
|
||
### 13.1 Jenkins Job 名称
|
||
建议固定为一个或少数几个通用参数化任务,例如:
|
||
- `pytest-auto-runner`
|
||
- 如果分项目,可 `pytest-auto-runner-zhyy`
|
||
|
||
不建议“一计划一 Job”。
|
||
|
||
### 13.2 构建参数
|
||
|
||
```text
|
||
EXECUTION_ID
|
||
CALLBACK_TOKEN
|
||
PLATFORM_BASE_URL
|
||
ENV_CODE
|
||
RUN_MODE
|
||
TRIGGER_TYPE
|
||
```
|
||
|
||
#### 含义
|
||
- `EXECUTION_ID`: 平台执行主键
|
||
- `CALLBACK_TOKEN`: 回调鉴权 token
|
||
- `PLATFORM_BASE_URL`: 平台地址,如 `http://xxx/api`
|
||
- `ENV_CODE`: st/dev/pre
|
||
- `RUN_MODE`: 1串行 2并行
|
||
- `TRIGGER_TYPE`: 1单条 2计划
|
||
|
||
### 13.3 可选参数
|
||
|
||
```text
|
||
GIT_BRANCH
|
||
PYTEST_ARGS
|
||
REPORT_BASE_URL
|
||
```
|
||
|
||
存放位置建议:
|
||
- 平台配置中心 / 环境变量 / 常量文件
|
||
- 不建议写死在 controller
|
||
|
||
### 13.4 Jenkins Build API 请求方式
|
||
|
||
推荐后端调用:
|
||
|
||
```text
|
||
POST /job/{jobName}/buildWithParameters
|
||
```
|
||
|
||
鉴权建议:
|
||
- Basic Auth + API Token
|
||
- 若 Jenkins 开启 crumb 防护,还要先请求 crumb
|
||
|
||
---
|
||
|
||
## 14. Jenkins 回调与结果回写逻辑
|
||
|
||
### 14.1 平台触发 Jenkins 时的处理
|
||
|
||
`AutomationService.trigger_execution(...)` 需要:
|
||
|
||
1. 生成 `callback_token`
|
||
2. 创建执行单和明细
|
||
3. 更新主状态 `0 -> 1`
|
||
4. 调 Jenkins API
|
||
5. 成功:
|
||
- 主状态 `1 -> 2`
|
||
- 保存 queue/build 基础信息
|
||
6. 失败:
|
||
- 主状态 `1 -> 7`
|
||
- 保存错误信息到 `trigger_message`
|
||
|
||
### 14.2 pytest 项目开始时的处理
|
||
|
||
pytest 启动脚本:
|
||
1. 根据 Jenkins 参数拿到 `EXECUTION_ID`
|
||
2. 调平台 `/execution/case/pull`
|
||
3. 成功拿清单后调用 `/execution/start`
|
||
4. 开始逐条执行
|
||
|
||
### 14.3 pytest 每执行一条用例
|
||
|
||
执行器需要:
|
||
1. 将 `case_id` 定位到 pytest 对应测试项
|
||
2. 执行完成后回调 `/execution/case/result`
|
||
3. 回调内容至少包括:
|
||
- executionCaseId / caseId
|
||
- status
|
||
- durationSeconds
|
||
- nodeid
|
||
- errorMessage/resultMessage
|
||
|
||
### 14.4 pytest 全量执行结束
|
||
|
||
执行器需要:
|
||
1. 汇总总数
|
||
2. 计算开始/结束时间
|
||
3. 发布 report_url(如 Allure)
|
||
4. 调 `/execution/finish`
|
||
|
||
### 14.5 Jenkins 失败兜底
|
||
|
||
即使 pytest 未回调成功,Jenkins `post { failure { ... } }` 阶段也应该:
|
||
- 至少调用 `/execution/abort` 或 `/execution/finish(status=5)`
|
||
- 避免平台主单永久卡在 `2/3`
|
||
|
||
---
|
||
|
||
## 15. 详细执行流程
|
||
|
||
### 15.1 单条用例执行流程
|
||
|
||
```text
|
||
前端点击“执行自动化”
|
||
-> POST /api/automation/case/run
|
||
-> controller 校验参数
|
||
-> service 查询 test_case
|
||
-> 校验 is_auto=1
|
||
-> dao 创建 auto_execution
|
||
-> dao 创建 auto_execution_case
|
||
-> service 调 Jenkins
|
||
-> Jenkins 接受构建
|
||
-> 平台主单状态变更为排队中
|
||
-> pytest 项目启动
|
||
-> 拉取 execution 清单
|
||
-> 回调 start
|
||
-> 执行 case_id 对应 pytest 用例
|
||
-> 回调 case/result
|
||
-> 回调 finish
|
||
-> 平台更新 execution 汇总
|
||
```
|
||
|
||
### 15.2 计划批量执行流程
|
||
|
||
```text
|
||
前端点击“执行自动化用例”
|
||
-> POST /api/automation/plan/run
|
||
-> controller 校验参数
|
||
-> service 查询 plan_case + test_case
|
||
-> 过滤 is_auto=1
|
||
-> dao 创建 auto_execution
|
||
-> dao 批量创建 auto_execution_case
|
||
-> service 调 Jenkins
|
||
-> Jenkins 启动 pytest 项目
|
||
-> pytest 按 execution_id 拉取清单
|
||
-> 回调 execution/start
|
||
-> 按顺序或并行执行每个 case_id
|
||
-> 每条执行后回调 case/result
|
||
-> 平台同步回写 plan_case
|
||
-> 全部完成后回调 finish
|
||
-> 平台刷新 auto_execution 汇总
|
||
-> 平台刷新 test_plan 状态
|
||
```
|
||
|
||
---
|
||
|
||
## 16. Controller / Service / DAO 详细职责设计
|
||
|
||
### 16.1 Controller 层
|
||
新增 `AutomationController`,建议方法:
|
||
|
||
- `case_run()`
|
||
- `plan_run()`
|
||
- `execution_list()`
|
||
- `execution_detail()`
|
||
- `execution_case_list()`
|
||
- `execution_case_pull()`
|
||
- `execution_queued()`
|
||
- `execution_start()`
|
||
- `execution_case_result()`
|
||
- `execution_finish()`
|
||
- `execution_abort()`
|
||
|
||
Controller 责任:
|
||
- 参数读取 `_get`
|
||
- 基础必填校验
|
||
- 调 service
|
||
- 返回统一结构
|
||
|
||
不要在 controller 里直接写 SQL 或请求 Jenkins。
|
||
|
||
---
|
||
|
||
### 16.2 Service 层
|
||
新增 `AutomationService`,建议职责:
|
||
|
||
#### 触发类
|
||
- `create_case_execution(session, req_data, user_id)`
|
||
- `create_plan_execution(session, req_data, user_id)`
|
||
- `trigger_jenkins(session, execution_id, job_name)`
|
||
|
||
#### 查询类
|
||
- `get_execution_detail(session, execution_id)`
|
||
- `list_executions(session, filters, page, size)`
|
||
- `list_execution_cases(session, execution_id, filters, page, size)`
|
||
- `pull_execution_cases(session, execution_id, callback_token)`
|
||
|
||
#### 回调类
|
||
- `mark_execution_queued(...)`
|
||
- `mark_execution_started(...)`
|
||
- `save_case_result(...)`
|
||
- `finish_execution(...)`
|
||
- `abort_execution(...)`
|
||
|
||
#### 聚合类
|
||
- `refresh_execution_summary(session, execution_id)`
|
||
- `sync_plan_case_result(session, execution_case)`
|
||
- `refresh_plan_status(session, plan_id)`
|
||
|
||
Service 层应承接主要业务逻辑与状态流转。
|
||
|
||
---
|
||
|
||
### 16.3 DAO 层
|
||
新增 `AutomationDao`,建议方法:
|
||
|
||
#### 主表
|
||
- `create_execution(session, add_info)`
|
||
- `update_execution_by_id(session, execution_id, update_info)`
|
||
- `get_execution_by_id(session, execution_id)`
|
||
- `list_execution_by_filters(session, filters, page, size)`
|
||
|
||
#### 明细表
|
||
- `batch_create_execution_cases(session, batch_info_list)`
|
||
- `get_execution_case_by_id(session, execution_case_id)`
|
||
- `get_execution_case_by_unique(session, execution_id, case_id, plan_case_id=None)`
|
||
- `update_execution_case_by_id(session, execution_case_id, update_info)`
|
||
- `list_execution_case_by_filters(session, filters, page, size)`
|
||
|
||
#### 聚合
|
||
- `count_execution_case_summary(session, execution_id)`
|
||
|
||
#### 执行清单查询
|
||
- `query_plan_auto_cases(session, plan_id, round_no=None, case_ids=None)`
|
||
- `query_case_auto_item(session, case_id)`
|
||
|
||
DAO 保持与当前项目风格一致:
|
||
- 独立 CRUD
|
||
- `session.done(close=False)`
|
||
- 失败返回 `(0, err_msg)` / 查询返回对象
|
||
|
||
---
|
||
|
||
## 17. 与现有代码的修改点清单
|
||
|
||
### 17.1 新增文件
|
||
|
||
1. `app/api/model/automationModel.py`
|
||
2. `app/api/dao/automationDao.py`
|
||
3. `app/api/service/automationService.py`
|
||
4. `app/api/controller/automationController.py`
|
||
5. `common/jenkinsRequest.py`
|
||
|
||
### 17.2 修改文件
|
||
|
||
#### `app/api/views.py`
|
||
新增路由注册:
|
||
- `/automation/case/run`
|
||
- `/automation/plan/run`
|
||
- `/automation/execution/list`
|
||
- `/automation/execution/detail`
|
||
- `/automation/execution/case/list`
|
||
- `/automation/execution/case/pull`
|
||
- `/automation/execution/queued`
|
||
- `/automation/execution/start`
|
||
- `/automation/execution/case/result`
|
||
- `/automation/execution/finish`
|
||
- `/automation/execution/abort`
|
||
|
||
#### `app/api/service/planService.py`
|
||
建议增加公共方法:
|
||
- `refresh_plan_status(session, plan_id)`
|
||
|
||
把 `PlanController._update_plan_status()` 中的逻辑迁移出来,避免 controller 内部藏业务逻辑。
|
||
|
||
#### `app/api/controller/planController.py`
|
||
- `plan_case_execute()` 仍保留手工执行。
|
||
- 自动化执行入口不要继续加到这个方法里,而是走新 automation 接口。
|
||
|
||
#### `const.py`
|
||
建议新增 Jenkins 配置:
|
||
- `JENKINS_BASE_URL`
|
||
- `JENKINS_USER`
|
||
- `JENKINS_TOKEN`
|
||
- `JENKINS_DEFAULT_JOB`
|
||
- `AUTOMATION_CALLBACK_SECRET`
|
||
- `PLATFORM_BASE_URL`
|
||
|
||
> 生产上更建议来自环境变量,但当前项目已有常量直写风格,第一期可先保持一致。
|
||
|
||
---
|
||
|
||
## 18. `common/jenkinsRequest.py` 设计建议
|
||
|
||
参考 `common/getRequest.py` 风格,但 Jenkins 需要更灵活处理:
|
||
|
||
建议封装:
|
||
- `get_crumb()`
|
||
- `build_with_parameters(job_name, params)`
|
||
- `get_build_info(job_name, build_number)` 可选
|
||
|
||
### 建议返回内容
|
||
触发后尽量返回:
|
||
- success
|
||
- status_code
|
||
- queue_id(如果能解析到)
|
||
- location header
|
||
- error_message
|
||
|
||
如果当前 Jenkins 网关层不方便拿 queueId,也可以第一期只判断触发成功与否。
|
||
|
||
---
|
||
|
||
## 19. 回写 `plan_case` 的规则细化
|
||
|
||
每次 `/execution/case/result` 到达后:
|
||
|
||
如果 `auto_execution_case.plan_case_id` 不为空,则同步:
|
||
|
||
### 19.1 状态映射
|
||
- `2 通过` -> `plan_case.status = 1`
|
||
- `3 失败` -> `plan_case.status = 2`
|
||
- `4 阻塞` -> `plan_case.status = 3`
|
||
- `5 跳过 / 6未找到 / 7取消` -> 默认不改 `plan_case.status`
|
||
|
||
### 19.2 文本字段
|
||
- `plan_case.actual_result`:
|
||
- 成功:记录 `resultMessage`
|
||
- 失败:优先写 `errorMessage`
|
||
- `plan_case.executed_time`:写 `finished_time` 或当前时间
|
||
- `plan_case.execution_duration`:写 `duration_seconds`
|
||
- `defect_links / attachments`:本期不自动写,后续有缺陷系统集成再扩展
|
||
|
||
### 19.3 幂等性
|
||
同一个 `executionCaseId` 多次回调时:
|
||
- 允许覆盖 `status / duration / message`
|
||
- 但如果主状态已结束(4/5/6),应限制后续异常回调只做日志记录或按最后一次有效值覆盖,避免状态混乱
|
||
|
||
---
|
||
|
||
## 20. 聚合汇总逻辑
|
||
|
||
### 20.1 执行汇总字段计算规则
|
||
基于 `auto_execution_case` 统计:
|
||
- `total_count = count(*)`
|
||
- `pending_count = status=0`
|
||
- `running_count = status=1`
|
||
- `passed_count = status=2`
|
||
- `failed_count = status=3`
|
||
- `blocked_count = status=4`
|
||
- `skipped_count = status=5`
|
||
- `not_found_count = status=6`
|
||
|
||
### 20.2 主状态自动判定规则
|
||
在 `refresh_execution_summary` 中:
|
||
|
||
1. 若存在 `running_count > 0` -> 主状态至少为 `3`
|
||
2. 若全部结束:
|
||
- `failed + blocked + not_found > 0` -> `5`
|
||
- 否则 -> `4`
|
||
3. 若任务被外部取消 -> `6`
|
||
4. 若触发阶段失败 -> `7`
|
||
|
||
### 20.3 结束时间规则
|
||
当全部明细都进入结束态(2/3/4/5/6/7)时:
|
||
- 自动补 `end_time`
|
||
- 若 `start_time` 已有,则计算 `duration_seconds`
|
||
|
||
---
|
||
|
||
## 21. 计划执行选取逻辑
|
||
|
||
计划执行时,从 `plan_case` 选出真正需要跑的明细。
|
||
|
||
### 21.1 查询条件
|
||
- `PlanCase.plan_id = planId`
|
||
- 如果传 `roundNo`:`PlanCase.round_no = roundNo`
|
||
- 如果传 `caseIds`:`PlanCase.case_id in caseIds`
|
||
- 关联 `TestCase.is_delete = 0`
|
||
- `TestCase.is_auto = 1`
|
||
- 如有需要,也可加 `TestCase.status = 1 or 4`(正常/评审通过)
|
||
|
||
### 21.2 排序规则
|
||
默认:
|
||
- `plan_case.id asc`
|
||
|
||
### 21.3 去重规则
|
||
如果同一计划下同一轮次允许一个 `case_id` 出现多次:
|
||
- 不去重,按 `plan_case.id` 逐条执行
|
||
- 这样更符合计划执行场景
|
||
|
||
如果你明确保证不重复,也可唯一化。
|
||
|
||
---
|
||
|
||
## 22. 单条执行选取逻辑
|
||
|
||
输入 `caseId` 后:
|
||
- 直接查询 `test_case`
|
||
- 校验 `is_auto=1`
|
||
- 创建 1 条 `auto_execution_case`
|
||
- `plan_case_id` 为空
|
||
|
||
这样单条执行与计划执行复用同一套执行主流程。
|
||
|
||
---
|
||
|
||
## 23. 幂等与并发控制
|
||
|
||
### 23.1 重复点击执行按钮
|
||
建议在创建任务前校验:
|
||
- 是否存在同一 `planId + envCode + status in (0,1,2,3)` 的运行中任务
|
||
- 是否存在同一 `caseId + envCode + status in (0,1,2,3)` 的运行中任务
|
||
|
||
策略建议:
|
||
- 默认不允许重复触发,返回“已有执行中任务”
|
||
- 如果后续需要“强制重跑”,再加 `force=true`
|
||
|
||
### 23.2 回调幂等
|
||
对于 `/execution/case/result`:
|
||
- 若同一条记录已是最终状态,再收到相同状态回调,应视为幂等成功
|
||
- 若收到不同终态,以最后一次回调覆盖,但写日志
|
||
|
||
### 23.3 事务一致性
|
||
`save_case_result()` 推荐事务内做:
|
||
1. 更新 `auto_execution_case`
|
||
2. 更新 `plan_case`
|
||
3. 提交
|
||
4. 再调用聚合更新 `auto_execution`
|
||
|
||
如果要更强一致性,也可在同一事务完成,但当前项目的 session 风格是轻量提交,可分两步。
|
||
|
||
---
|
||
|
||
## 24. 错误场景与兜底策略
|
||
|
||
### 24.1 用例不存在
|
||
- 单条执行:直接返回失败
|
||
- 计划执行:忽略已删除用例,仅对有效自动化用例建单;若最终为空则报错
|
||
|
||
### 24.2 用例未接入自动化
|
||
- 单条执行:直接报错“该用例未实现自动化”
|
||
- 计划执行:过滤掉 `is_auto=0`;若全部都不是自动化则报错
|
||
|
||
### 24.3 Jenkins 触发失败
|
||
- `auto_execution.status = 7`
|
||
- `trigger_message` 存失败原因
|
||
|
||
### 24.4 pytest 找不到 `case_id` 对应实现
|
||
- 回调单条明细 `status = 6`
|
||
- 主单最终通常为 `5`
|
||
|
||
### 24.5 部分用例执行完,finish 回调丢失
|
||
- Jenkins `post` 阶段兜底调 `/finish` 或 `/abort`
|
||
- 平台可后续增加定时补偿任务,扫描长时间停留在 `3 执行中` 的记录
|
||
|
||
### 24.6 平台回调接口鉴权失败
|
||
- 返回失败
|
||
- 自动化项目应重试
|
||
- 平台日志记录 `executionId`
|
||
|
||
---
|
||
|
||
## 25. 接口返回码与错误语义建议
|
||
|
||
当前项目 `const.py` 中错误码较泛,第一期可以沿用现有:
|
||
- 40009 新增失败
|
||
- 40011 获取失败
|
||
- 40012 更新失败
|
||
|
||
但为了 AI 编码更明确,建议新增语义化错误码:
|
||
|
||
```python
|
||
40020: '自动化执行任务创建失败'
|
||
40021: '自动化用例不存在'
|
||
40022: '该用例未接入自动化'
|
||
40023: '计划下无可执行自动化用例'
|
||
40024: 'Jenkins 触发失败'
|
||
40025: '执行记录不存在'
|
||
40026: '回调鉴权失败'
|
||
40027: '执行结果回写失败'
|
||
```
|
||
|
||
> 若你想最小改动,也可先不加新错误码,统一用已有 `40009/40011/40012`。
|
||
|
||
---
|
||
|
||
## 26. 权限设计建议
|
||
|
||
建议新增权限点:
|
||
- `automation:run`
|
||
- `automation:list`
|
||
- `automation:detail`
|
||
- `automation:callback`
|
||
|
||
其中:
|
||
- 前端用户接口需要登录 + `automation:run/list/detail`
|
||
- Jenkins/pytest 回调接口建议:
|
||
- 放到 `should_skip_auth()` 白名单中,绕过用户 token
|
||
- 改为内部 token/header 校验
|
||
|
||
否则自动化项目拿用户 token 不稳定。
|
||
|
||
---
|
||
|
||
## 27. 回调鉴权设计
|
||
|
||
### 27.1 推荐方案
|
||
每次创建执行任务时生成 `callback_token`,存 `auto_execution.callback_token`。
|
||
|
||
Jenkins/pytest 回调时传:
|
||
```text
|
||
X-CALLBACK-TOKEN: {callback_token}
|
||
```
|
||
|
||
平台校验:
|
||
- `executionId` 存在
|
||
- header token 与库中一致
|
||
|
||
### 27.2 好处
|
||
- 不依赖用户登录 token
|
||
- 每次执行隔离
|
||
- token 泄露影响范围仅单次任务
|
||
|
||
---
|
||
|
||
## 28. pytest 自动化项目对接契约
|
||
|
||
虽然本期不写自动化项目代码,但后端方案必须给到契约。
|
||
|
||
### 28.1 自动化项目输入
|
||
来自 Jenkins 参数:
|
||
- `EXECUTION_ID`
|
||
- `CALLBACK_TOKEN`
|
||
- `PLATFORM_BASE_URL`
|
||
- `ENV_CODE`
|
||
- `RUN_MODE`
|
||
|
||
### 28.2 自动化项目行为
|
||
1. 调 `/execution/case/pull`
|
||
2. 将 `case_id` 映射到 pytest 测试项
|
||
3. 单条执行后调 `/execution/case/result`
|
||
4. 全部结束调 `/execution/finish`
|
||
|
||
### 28.3 `case_id` 到 pytest 的映射方式
|
||
你已经确定桥梁是 `case_id`,建议自动化项目采用以下之一:
|
||
|
||
#### 方式 A:pytest marker
|
||
```python
|
||
@pytest.mark.case_id(1001)
|
||
def test_login_success():
|
||
...
|
||
```
|
||
|
||
执行器启动时收集 marker,建立 `case_id -> nodeid` 映射。
|
||
|
||
#### 方式 B:维护映射文件
|
||
如 `case_map.json`
|
||
|
||
但从维护性看,**推荐 marker 方式**。
|
||
|
||
### 28.4 找不到映射时
|
||
回调:
|
||
- `status=6`
|
||
- `resultMessage='case_id 未映射到 pytest 用例'`
|
||
|
||
---
|
||
|
||
## 29. MVC 代码落地顺序建议(交给 AI 编码的实施计划)
|
||
|
||
### Phase 1:数据层
|
||
1. 新建数据库表:
|
||
- `auto_execution`
|
||
- `auto_execution_case`
|
||
2. 新建 `automationModel.py`
|
||
3. 新建 `automationDao.py`
|
||
4. 编写:
|
||
- 创建主单
|
||
- 批量创建明细
|
||
- 查询详情/列表
|
||
- 聚合汇总
|
||
|
||
### Phase 2:服务层
|
||
1. 新建 `automationService.py`
|
||
2. 实现:
|
||
- 单条执行建单
|
||
- 计划执行建单
|
||
- Jenkins 触发
|
||
- 执行清单拉取
|
||
- 单条结果回写
|
||
- 整体完成回写
|
||
- 汇总刷新
|
||
3. 把 `PlanController._update_plan_status()` 逻辑迁移成服务方法
|
||
|
||
### Phase 3:控制器与路由
|
||
1. 新建 `automationController.py`
|
||
2. 在 `views.py` 注册接口
|
||
3. 增加回调接口白名单处理
|
||
|
||
### Phase 4:Jenkins 客户端
|
||
1. 新建 `common/jenkinsRequest.py`
|
||
2. 封装 Basic Auth + buildWithParameters
|
||
3. 在 service 中接入
|
||
|
||
### Phase 5:联调准备
|
||
1. 给 pytest 项目提供接口文档
|
||
2. 给 Jenkins Pipeline 提供参数清单
|
||
3. 约定回调协议
|
||
|
||
---
|
||
|
||
## 30. 推荐的 AI 编码拆分任务
|
||
|
||
为了让 AI 更稳地写代码,建议拆成 5 个子任务:
|
||
|
||
### 任务 1:建模与 DAO
|
||
输出:
|
||
- `automationModel.py`
|
||
- `automationDao.py`
|
||
|
||
### 任务 2:查询与列表接口
|
||
输出:
|
||
- execution list/detail/case list
|
||
|
||
### 任务 3:单条执行 / 计划执行建单逻辑
|
||
输出:
|
||
- case run
|
||
- plan run
|
||
- Jenkins trigger
|
||
|
||
### 任务 4:回调与状态流转
|
||
输出:
|
||
- pull/start/case_result/finish/abort
|
||
- plan_case 回写
|
||
- plan 状态更新
|
||
|
||
### 任务 5:Jenkins 客户端与配置收口
|
||
输出:
|
||
- `common/jenkinsRequest.py`
|
||
- `const.py` 新配置
|
||
- 回调白名单
|
||
|
||
---
|
||
|
||
## 31. 最终推荐实现决策
|
||
|
||
### 必做
|
||
1. 新增 `auto_execution`、`auto_execution_case`
|
||
2. 独立 automation MVC 模块
|
||
3. 平台只传 `execution_id` 给 Jenkins
|
||
4. pytest 项目按 `execution_id` 拉清单
|
||
5. 单条回写 + 整体回写
|
||
6. 回写 `plan_case` 和 `test_plan` 状态
|
||
|
||
### 建议做
|
||
1. `callback_token` 鉴权
|
||
2. 主/明细状态机
|
||
3. 幂等处理
|
||
4. Jenkins 失败兜底回调
|
||
|
||
### 可后置
|
||
1. 并行执行
|
||
2. 失败重跑
|
||
3. 定时补偿
|
||
4. 执行事件表
|
||
5. 自动创建缺陷/附件
|
||
|
||
---
|
||
|
||
## 32. 可直接交给 AI 的开发说明摘要
|
||
|
||
你可以把下面这段直接给 AI 作为实现要求:
|
||
|
||
1. 基于当前 Flask MVC 结构,新增 automation 域:controller/service/dao/model。
|
||
2. 新增表 `auto_execution`、`auto_execution_case`,字段按本方案实现。
|
||
3. 提供接口:
|
||
- `POST /api/automation/case/run`
|
||
- `POST /api/automation/plan/run`
|
||
- `GET /api/automation/execution/list`
|
||
- `GET /api/automation/execution/detail`
|
||
- `GET /api/automation/execution/case/list`
|
||
- `GET /api/automation/execution/case/pull`
|
||
- `POST /api/automation/execution/queued`
|
||
- `POST /api/automation/execution/start`
|
||
- `POST /api/automation/execution/case/result`
|
||
- `POST /api/automation/execution/finish`
|
||
- `POST /api/automation/execution/abort`
|
||
4. 单条执行通过 `test_case.id` 建单,计划执行通过 `plan_case + test_case.is_auto=1` 批量建单。
|
||
5. 主状态与明细状态按本方案的枚举实现。
|
||
6. `/case/result` 回调时同步更新 `plan_case.status / actual_result / executed_time / execution_duration`。
|
||
7. 每次明细回写后聚合更新 `auto_execution` 的计数和主状态。
|
||
8. 计划执行时,执行结果变化后要刷新 `test_plan.status`,把当前 `PlanController._update_plan_status()` 逻辑迁到 service 层复用。
|
||
9. 新增 `common/jenkinsRequest.py`,封装 Jenkins `buildWithParameters` 调用。
|
||
10. Jenkins 参数至少包含:`EXECUTION_ID`、`CALLBACK_TOKEN`、`PLATFORM_BASE_URL`、`ENV_CODE`、`RUN_MODE`、`TRIGGER_TYPE`。
|
||
11. 回调接口采用 `X-CALLBACK-TOKEN` 鉴权,并加入免登录白名单。
|
||
|
||
---
|
||
|
||
## 33. PostgreSQL 建表 SQL(可直接落库)
|
||
|
||
> 说明:以下 SQL 仅覆盖本次自动化执行接入新增对象,不包含已有 `test_case / plan_case / test_plan` 表。
|
||
> 当前代码已按 PostgreSQL + SQLAlchemy 映射实现,建议先在测试环境执行 SQL,再启动服务联调。
|
||
|
||
### 33.1 自动化执行主表
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS auto_execution (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
execution_no VARCHAR(64) NOT NULL UNIQUE,
|
||
trigger_type SMALLINT NOT NULL,
|
||
project_id BIGINT NOT NULL,
|
||
plan_id BIGINT,
|
||
plan_round_no INTEGER,
|
||
source_case_id BIGINT,
|
||
env_code VARCHAR(32) NOT NULL,
|
||
run_mode SMALLINT DEFAULT 1,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
jenkins_job_name VARCHAR(128),
|
||
jenkins_queue_id BIGINT,
|
||
jenkins_build_number BIGINT,
|
||
jenkins_build_url VARCHAR(512),
|
||
console_url VARCHAR(512),
|
||
report_url VARCHAR(512),
|
||
total_count INTEGER DEFAULT 0,
|
||
pending_count INTEGER DEFAULT 0,
|
||
running_count INTEGER DEFAULT 0,
|
||
passed_count INTEGER DEFAULT 0,
|
||
failed_count INTEGER DEFAULT 0,
|
||
blocked_count INTEGER DEFAULT 0,
|
||
skipped_count INTEGER DEFAULT 0,
|
||
not_found_count INTEGER DEFAULT 0,
|
||
trigger_by BIGINT,
|
||
trigger_source VARCHAR(32) DEFAULT 'platform',
|
||
trigger_message TEXT,
|
||
start_time TIMESTAMP,
|
||
end_time TIMESTAMP,
|
||
duration_seconds INTEGER,
|
||
callback_token VARCHAR(128),
|
||
ext JSONB DEFAULT '{}'::jsonb,
|
||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
### 33.2 自动化执行明细表
|
||
|
||
```sql
|
||
CREATE TABLE IF NOT EXISTS auto_execution_case (
|
||
id BIGSERIAL PRIMARY KEY,
|
||
execution_id BIGINT NOT NULL,
|
||
plan_case_id BIGINT,
|
||
case_id BIGINT NOT NULL,
|
||
case_key VARCHAR(64),
|
||
case_title VARCHAR(255),
|
||
run_order INTEGER DEFAULT 0,
|
||
status SMALLINT NOT NULL DEFAULT 0,
|
||
pytest_nodeid VARCHAR(512),
|
||
result_message TEXT,
|
||
error_message TEXT,
|
||
stack_trace TEXT,
|
||
report_url VARCHAR(512),
|
||
duration_seconds INTEGER,
|
||
started_time TIMESTAMP,
|
||
finished_time TIMESTAMP,
|
||
retry_count INTEGER DEFAULT 0,
|
||
ext JSONB DEFAULT '{}'::jsonb,
|
||
created_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||
updated_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||
);
|
||
```
|
||
|
||
### 33.3 索引
|
||
|
||
```sql
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_plan_id ON auto_execution(plan_id);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_project_id ON auto_execution(project_id);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_status ON auto_execution(status);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_created_time ON auto_execution(created_time DESC);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_trigger_by ON auto_execution(trigger_by);
|
||
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_case_execution_id ON auto_execution_case(execution_id);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_case_case_id ON auto_execution_case(case_id);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_case_plan_case_id ON auto_execution_case(plan_case_id);
|
||
CREATE INDEX IF NOT EXISTS idx_auto_execution_case_status ON auto_execution_case(status);
|
||
```
|
||
|
||
### 33.4 唯一约束建议
|
||
|
||
当前代码在 `get_execution_case_by_unique()` 中按:
|
||
- `execution_id + case_id`
|
||
- 有 `plan_case_id` 时再附加 `plan_case_id`
|
||
|
||
如果你确认同一执行内允许同一个 `case_id` 因不同 `plan_case_id` 出现多次,建议使用下面唯一索引:
|
||
|
||
```sql
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uk_auto_execution_case_exec_plan_case
|
||
ON auto_execution_case(execution_id, plan_case_id, case_id);
|
||
```
|
||
|
||
如果你确认同一执行中 `case_id` 不会重复,则可改为更强约束:
|
||
|
||
```sql
|
||
CREATE UNIQUE INDEX IF NOT EXISTS uk_auto_execution_case_exec_case
|
||
ON auto_execution_case(execution_id, case_id);
|
||
```
|
||
|
||
> 推荐和当前业务保持一致:计划里可能重复挂同一个 case 时,用第一种。
|
||
|
||
### 33.5 外键建议
|
||
|
||
如果你当前库中已有规范外键管理,可以加;如果历史库本身较轻、线上变更风险高,第一期可不加外键,只保留业务层约束。
|
||
|
||
可选外键:
|
||
|
||
```sql
|
||
ALTER TABLE auto_execution_case
|
||
ADD CONSTRAINT fk_auto_execution_case_execution_id
|
||
FOREIGN KEY (execution_id) REFERENCES auto_execution(id);
|
||
|
||
ALTER TABLE auto_execution_case
|
||
ADD CONSTRAINT fk_auto_execution_case_plan_case_id
|
||
FOREIGN KEY (plan_case_id) REFERENCES plan_case(id);
|
||
|
||
ALTER TABLE auto_execution_case
|
||
ADD CONSTRAINT fk_auto_execution_case_case_id
|
||
FOREIGN KEY (case_id) REFERENCES test_case(id);
|
||
```
|
||
|
||
### 33.6 updated_time 自动更新时间建议
|
||
|
||
当前 SQLAlchemy 已声明 `server_onupdate`,但 PostgreSQL 本身不会仅靠字段定义自动刷新 `updated_time`。
|
||
如果你希望数据库层自动维护更新时间,建议补 trigger:
|
||
|
||
```sql
|
||
CREATE OR REPLACE FUNCTION update_modified_column()
|
||
RETURNS TRIGGER AS $
|
||
BEGIN
|
||
NEW.updated_time = CURRENT_TIMESTAMP;
|
||
RETURN NEW;
|
||
END;
|
||
$ language 'plpgsql';
|
||
|
||
DROP TRIGGER IF EXISTS trg_auto_execution_updated_time ON auto_execution;
|
||
CREATE TRIGGER trg_auto_execution_updated_time
|
||
BEFORE UPDATE ON auto_execution
|
||
FOR EACH ROW
|
||
EXECUTE PROCEDURE update_modified_column();
|
||
|
||
DROP TRIGGER IF EXISTS trg_auto_execution_case_updated_time ON auto_execution_case;
|
||
CREATE TRIGGER trg_auto_execution_case_updated_time
|
||
BEFORE UPDATE ON auto_execution_case
|
||
FOR EACH ROW
|
||
EXECUTE PROCEDURE update_modified_column();
|
||
```
|
||
|
||
### 33.7 权限初始化 SQL
|
||
|
||
你当前代码新增了以下权限点:
|
||
- `automation:run`
|
||
- `automation:list`
|
||
- `automation:detail`
|
||
|
||
如果你们 RBAC 表结构支持直接插入权限编码,可参考下面 SQL,字段名按你实际库调整:
|
||
|
||
```sql
|
||
-- 以下为示例,请按实际 rbac_permission 表结构调整字段
|
||
INSERT INTO rbac_permission (name, code, type, parent_id, created_time, updated_time)
|
||
VALUES
|
||
('自动化执行', 'automation:run', 2, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||
('自动化执行列表', 'automation:list', 2, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP),
|
||
('自动化执行详情', 'automation:detail', 2, NULL, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||
ON CONFLICT (code) DO NOTHING;
|
||
```
|
||
|
||
如果你们权限还要挂菜单或角色,再补:
|
||
- 角色权限关联表
|
||
- 菜单权限关联表
|
||
|
||
---
|
||
|
||
## 34. pytest + Jenkins 联调请求示例
|
||
|
||
以下示例是按你当前后端已实现接口整理的,可直接给自动化项目同学或 Jenkins Pipeline 使用。
|
||
|
||
### 34.1 Jenkins 触发参数约定
|
||
|
||
平台调用 Jenkins `buildWithParameters` 时传:
|
||
|
||
```text
|
||
EXECUTION_ID=123
|
||
CALLBACK_TOKEN=abc123token
|
||
PLATFORM_BASE_URL=http://127.0.0.1:5010/it/api
|
||
ENV_CODE=st
|
||
RUN_MODE=1
|
||
TRIGGER_TYPE=2
|
||
```
|
||
|
||
其中:
|
||
- `CALLBACK_TOKEN`:用于 `/automation/execution/case/pull`
|
||
- `X-CALLBACK-SECRET`:用于回调写入接口,由 Jenkins 环境变量统一注入
|
||
|
||
---
|
||
|
||
### 34.2 Jenkins Pipeline 示例
|
||
|
||
```groovy
|
||
pipeline {
|
||
agent any
|
||
parameters {
|
||
string(name: 'EXECUTION_ID', defaultValue: '', description: '平台执行ID')
|
||
string(name: 'CALLBACK_TOKEN', defaultValue: '', description: '拉取执行清单token')
|
||
string(name: 'PLATFORM_BASE_URL', defaultValue: 'http://127.0.0.1:5010/it/api', description: '平台地址')
|
||
string(name: 'ENV_CODE', defaultValue: 'st', description: '环境编码')
|
||
string(name: 'RUN_MODE', defaultValue: '1', description: '1串行 2并行')
|
||
string(name: 'TRIGGER_TYPE', defaultValue: '2', description: '1单条 2计划')
|
||
}
|
||
environment {
|
||
CALLBACK_SECRET = credentials('AUTOMATION_CALLBACK_SECRET')
|
||
}
|
||
stages {
|
||
stage('Checkout') {
|
||
steps {
|
||
checkout scm
|
||
}
|
||
}
|
||
stage('Notify Start') {
|
||
steps {
|
||
bat '''
|
||
curl -X POST "%PLATFORM_BASE_URL%/automation/execution/start" ^
|
||
-H "Content-Type: application/json" ^
|
||
-H "X-CALLBACK-SECRET: %CALLBACK_SECRET%" ^
|
||
-d "{\"executionId\": %EXECUTION_ID%, \"jobName\": \"pytest-auto-runner\", \"buildNumber\": %BUILD_NUMBER%, \"buildUrl\": \"%BUILD_URL%\", \"consoleUrl\": \"%BUILD_URL%console\"}"
|
||
'''
|
||
}
|
||
}
|
||
stage('Run Pytest') {
|
||
steps {
|
||
bat '''
|
||
python run_execution.py ^
|
||
--execution-id %EXECUTION_ID% ^
|
||
--callback-token %CALLBACK_TOKEN% ^
|
||
--platform-base-url %PLATFORM_BASE_URL% ^
|
||
--env-code %ENV_CODE% ^
|
||
--run-mode %RUN_MODE% ^
|
||
--callback-secret %CALLBACK_SECRET%
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
post {
|
||
failure {
|
||
bat '''
|
||
curl -X POST "%PLATFORM_BASE_URL%/automation/execution/abort" ^
|
||
-H "Content-Type: application/json" ^
|
||
-H "X-CALLBACK-SECRET: %CALLBACK_SECRET%" ^
|
||
-d "{\"executionId\": %EXECUTION_ID%, \"status\": 5, \"message\": \"Jenkins failed\", \"buildNumber\": %BUILD_NUMBER%, \"consoleUrl\": \"%BUILD_URL%console\"}"
|
||
'''
|
||
}
|
||
aborted {
|
||
bat '''
|
||
curl -X POST "%PLATFORM_BASE_URL%/automation/execution/abort" ^
|
||
-H "Content-Type: application/json" ^
|
||
-H "X-CALLBACK-SECRET: %CALLBACK_SECRET%" ^
|
||
-d "{\"executionId\": %EXECUTION_ID%, \"status\": 6, \"message\": \"Jenkins aborted\", \"buildNumber\": %BUILD_NUMBER%, \"consoleUrl\": \"%BUILD_URL%console\"}"
|
||
'''
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 34.3 pytest 项目:拉取执行清单示例
|
||
|
||
```bash
|
||
curl -X GET "http://127.0.0.1:5010/it/api/automation/execution/case/pull?executionId=123" \
|
||
-H "X-CALLBACK-TOKEN: abc123token"
|
||
```
|
||
|
||
响应示例:
|
||
|
||
```json
|
||
{
|
||
"code": 20000,
|
||
"data": {
|
||
"executionId": 123,
|
||
"executionNo": "AE202502120001",
|
||
"triggerType": 2,
|
||
"projectId": 88,
|
||
"planId": 2001,
|
||
"envCode": "st",
|
||
"runMode": 1,
|
||
"items": [
|
||
{
|
||
"executionCaseId": 501,
|
||
"planCaseId": 9001,
|
||
"caseId": 1001,
|
||
"caseKey": "TC-ZHYY-001",
|
||
"caseTitle": "登录成功",
|
||
"runOrder": 1
|
||
}
|
||
]
|
||
},
|
||
"msg": "success"
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
### 34.4 pytest 项目:开始执行回调示例
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/start" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"jobName": "pytest-auto-runner",
|
||
"buildNumber": 89,
|
||
"buildUrl": "http://jenkins/job/pytest-auto-runner/89/",
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console",
|
||
"startTime": "2025-02-12 10:00:00"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 34.5 pytest 项目:单条用例结果回调示例
|
||
|
||
#### 通过
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/case/result" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"executionCaseId": 501,
|
||
"caseId": 1001,
|
||
"status": 2,
|
||
"pytestNodeid": "tests/test_login.py::test_login_success",
|
||
"resultMessage": "assert success",
|
||
"errorMessage": "",
|
||
"stackTrace": "",
|
||
"durationSeconds": 12,
|
||
"reportUrl": "http://allure/case/1001",
|
||
"startedTime": "2025-02-12 10:01:00",
|
||
"finishedTime": "2025-02-12 10:01:12",
|
||
"ext": {
|
||
"attachments": []
|
||
}
|
||
}'
|
||
```
|
||
|
||
#### 失败
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/case/result" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"executionCaseId": 502,
|
||
"caseId": 1002,
|
||
"status": 3,
|
||
"pytestNodeid": "tests/test_login.py::test_login_fail",
|
||
"resultMessage": "assert failed",
|
||
"errorMessage": "AssertionError: 登录失败提示不匹配",
|
||
"stackTrace": "Traceback ...",
|
||
"durationSeconds": 8,
|
||
"reportUrl": "http://allure/case/1002",
|
||
"startedTime": "2025-02-12 10:01:13",
|
||
"finishedTime": "2025-02-12 10:01:21"
|
||
}'
|
||
```
|
||
|
||
#### 未找到自动化实现
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/case/result" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"executionCaseId": 503,
|
||
"caseId": 1003,
|
||
"status": 6,
|
||
"resultMessage": "case_id 未映射到 pytest 用例",
|
||
"errorMessage": "",
|
||
"durationSeconds": 0,
|
||
"finishedTime": "2025-02-12 10:01:22"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 34.6 pytest 项目:执行完成回调示例
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/finish" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"buildNumber": 89,
|
||
"buildUrl": "http://jenkins/job/pytest-auto-runner/89/",
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console",
|
||
"reportUrl": "http://allure/report/89",
|
||
"startTime": "2025-02-12 10:00:00",
|
||
"endTime": "2025-02-12 10:08:00",
|
||
"durationSeconds": 480,
|
||
"summary": {
|
||
"total": 15,
|
||
"passed": 12,
|
||
"failed": 2,
|
||
"blocked": 0,
|
||
"skipped": 1,
|
||
"notFound": 0
|
||
},
|
||
"message": "执行完成"
|
||
}'
|
||
```
|
||
|
||
> 注意:当前后端实现里 `finish_execution()` 最终仍会以明细表聚合结果为准刷新主单状态,所以 pytest 回传 `summary` 更像补充信息,不是唯一可信来源。
|
||
|
||
---
|
||
|
||
### 34.7 Jenkins 失败/取消兜底回调示例
|
||
|
||
#### 失败
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/abort" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"status": 5,
|
||
"message": "Jenkins failed",
|
||
"buildNumber": 89,
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console"
|
||
}'
|
||
```
|
||
|
||
#### 取消
|
||
|
||
```bash
|
||
curl -X POST "http://127.0.0.1:5010/it/api/automation/execution/abort" \
|
||
-H "Content-Type: application/json" \
|
||
-H "X-CALLBACK-SECRET: your-callback-secret" \
|
||
-d '{
|
||
"executionId": 123,
|
||
"status": 6,
|
||
"message": "Jenkins aborted",
|
||
"buildNumber": 89,
|
||
"consoleUrl": "http://jenkins/job/pytest-auto-runner/89/console"
|
||
}'
|
||
```
|
||
|
||
---
|
||
|
||
### 34.8 pytest 侧最小执行脚本伪代码
|
||
|
||
```python
|
||
import requests
|
||
import pytest
|
||
|
||
|
||
def pull_cases(base_url, execution_id, callback_token):
|
||
resp = requests.get(
|
||
f"{base_url}/automation/execution/case/pull",
|
||
params={"executionId": execution_id},
|
||
headers={"X-CALLBACK-TOKEN": callback_token},
|
||
timeout=30,
|
||
)
|
||
resp.raise_for_status()
|
||
return resp.json()["data"]
|
||
|
||
|
||
def callback_case_result(base_url, callback_secret, payload):
|
||
requests.post(
|
||
f"{base_url}/automation/execution/case/result",
|
||
json=payload,
|
||
headers={"X-CALLBACK-SECRET": callback_secret},
|
||
timeout=30,
|
||
).raise_for_status()
|
||
|
||
|
||
if __name__ == '__main__':
|
||
# 1. 拉取 execution 清单
|
||
# 2. 建立 case_id -> pytest nodeid 映射
|
||
# 3. 逐条执行
|
||
# 4. 每条回调结果
|
||
# 5. 最后回调 finish
|
||
pass
|
||
```
|
||
|
||
---
|
||
|
||
## 35. 结论
|
||
|
||
最适合你当前项目的方案不是改造现有 `plan_case_execute`,而是:
|
||
|
||
- **在现有 MVC 上新增独立 automation 执行域**
|
||
- **以 `case_id` 作为功能用例与 pytest 自动化用例的桥梁**
|
||
- **平台负责建单、编排、回写和状态聚合**
|
||
- **Jenkins 负责调度**
|
||
- **pytest 项目负责按 `execution_id` 拉取清单并执行**
|
||
|
||
这样设计:
|
||
- 和你当前项目结构最兼容
|
||
- AI 最容易按层写代码
|
||
- 后续扩展单条执行、计划批量、并行、重跑都不会推翻重来
|