Files

90 lines
3.1 KiB
Python
Raw Permalink Normal View History

# -*- coding: utf-8 -*-
"""UAT 计划与执行支撑文档(docs/UAT计划与执行支撑.md,issue #92)一致性检查。
检查项:
1. 必备章节齐全(目标/角色/准入/用例集/执行计划/缺陷/准出/交付物/风险);
2. 验收流程六节点关键词存在(PRD §10:自测→内部评审→客户UAT→上线→质保观察→闭环);
3. NFR 量化指标齐全(PRD §9:P99≤1.8s / 丢失率≤0.02% / 可用性≥99.8% / 路由≥96.5% / 首屏≤2s);
4. 用例覆盖三类场景(监控A/优化B/LLM C)+ NFR + 移动端;
5. 引用配套资产路径存在(部署手册/采集网隔离文档/healthz 探针)。
用法:python _check_uat_plan.py
"""
import os
import sys
HERE = os.path.dirname(os.path.abspath(__file__))
DOC_PATH = os.path.join(HERE, "UAT计划与执行支撑.md")
REQUIRED_SECTIONS = [
"## 1. 目标与范围",
"## 2. 角色与职责(RACI)",
"## 3. 准入条件(Entry Criteria)",
"## 4. UAT 测试用例集",
"## 5. 执行计划与排期",
"## 6. 缺陷管理(Bug Triage)",
"## 7. 准出条件(Exit Criteria / 放行)",
"## 8. 交付物清单(UAT 阶段)",
"## 9. 风险与缓解",
]
# PRD §10 验收流程六节点
FLOW_KEYWORDS = ["自测", "内部评审", "客户 UAT", "上线", "质保观察", "闭环"]
# PRD §9 NFR 量化底线
NFR_KEYWORDS = ["1.8s", "0.02%", "99.8%", "96.5%", "2s"]
# 用例场景覆盖(用例编号前缀)
SCENARIO_PREFIXES = ["UAT-A-", "UAT-B-", "UAT-C-", "UAT-N-", "UAT-M-"]
# 必须存在的配套资产路径(相对仓库根)
# 注:部署手册(issue #91)随其 PR 合并后存在;本脚本校验本仓库主干已具备的资产。
ASSET_PATHS = [
"docs/产品设计文档_iAOP.md", # PRD(UAT 用例依据来源)
"docs/采集网只读隔离与网闸配置.md", # UAT-N-05 网络只读验证依据
"deploy/k8s/healthz/probe_availability.py", # UAT-N-02 可用性探针
]
def main() -> int:
failures = []
repo_root = os.path.dirname(HERE)
if not os.path.isfile(DOC_PATH):
print("FAIL\n - UAT 计划文档不存在:UAT计划与执行支撑.md")
return 1
text = open(DOC_PATH, "r", encoding="utf-8").read()
for sec in REQUIRED_SECTIONS:
if sec not in text:
failures.append(f"缺少必备章节:{sec}")
for kw in FLOW_KEYWORDS:
if kw not in text:
failures.append(f"缺少验收流程节点关键词:{kw}")
for kw in NFR_KEYWORDS:
if kw not in text:
failures.append(f"缺少 NFR 量化指标关键词:{kw}")
for prefix in SCENARIO_PREFIXES:
if prefix not in text:
failures.append(f"用例未覆盖场景前缀:{prefix}")
for rel in ASSET_PATHS:
if not os.path.isfile(os.path.join(repo_root, *rel.split("/"))):
failures.append(f"配套资产缺失:{rel}")
if failures:
print("FAIL")
for f in failures:
print(" -", f)
return 1
print("OK: UAT 计划章节/流程节点/NFR指标/场景覆盖/配套资产校验通过")
return 0
if __name__ == "__main__":
sys.exit(main())