QCov:从测试通过走向可审查的质量证据

QCov:从测试通过走向可审查的质量证据

“测试通过”经常是发布讨论里的最后一句话。真正难回答的是:它证明了哪项需求?它覆盖了哪一个风险?这次代码变更有没有让原来的证据失效?

如果需求写在文档里,测试结果写在报告里,风险写在评审记录里,变更又躺在 Git diff 里,团队很容易用一片绿色代替完整证据。测试数量、覆盖率百分比和 CI 状态都有用,但它们不会自动回答“这个业务义务是否已经被证明”。

QCov 就是为填补这个缺口设计的。它是开源的质量证据缺口引擎质量覆盖协议,核心问题只有一句:

Find what your tests still don’t prove.

这句话听起来简单,落到工程里却需要明确的测试义务、证据、映射、变更和策略。QCov 把这些记录放进本地、可版本化、可复查的流程里。

如果你先想检查 AI 生成的测试本身是否包含明确的静态证据,可以先读 AI Test Auditor:检查 AI 生成测试实际验证了什么。QCov 关注的是另一层:这些测试结果是否与需求、风险和变更形成可审查的证据链。

先回答一个经常被跳过的问题:测试通过究竟证明了什么?

假设一个退款需求是:累计退款金额不能超过支付金额。它至少可以提炼出一个业务不变量:

invariants:
  - id: INV-REFUND-001
    expression: total_refund <= paid_amount
requiredEvidence:
  behavior: [api_test]
  boundary: [property_test]
  data: [database_invariant]
  concurrency: [concurrency_test]
  idempotency: [idempotency_test]
  production: [runtime_monitor]

一个 API 测试通过,只能说明它在自己的执行范围内没有失败。它不等于边界、数据库不变量、并发、幂等和生产行为也都已经被证明。

QCov 的 TestingObligation 描述“为什么必须验证这件事”,QualityEvidence 描述“哪一条机器可读证据被绑定到这项义务”。引擎比较二者,输出每项义务当前的状态:

状态说明
COVERED声明所需的证据维度已经满足
PARTIAL一部分维度有证据,仍有缺口
MISSING需要的证据没有提供
UNKNOWN当前输入不足以做出可靠判断

仓库里的 Refund 示例会刻意保持为 PARTIAL:行为、边界和数据证据存在,并发、幂等性和生产证据仍未证明。这比把一个通过的 API 测试扩展成“退款逻辑已覆盖”更接近真实的质量讨论。

QCov 的核心链路:从工件到证据,中间必须有显式映射

QCov 的数据流可以概括为:

TestingObligation
    → 本地测试报告 / 观察工件(inventory)
    → 显式 EvidenceMapping
    → QualityEvidence
    → 确定性 Gap Engine
    → Markdown / JSON 报告

这里最重要的是中间那一步。QCov 不根据测试名称猜测试义务,也不根据覆盖率数字推断业务覆盖。EvidenceMapping 必须明确写出生产者和记录标识——例如某个 JUnit、Playwright 或 production-observation 记录——以及它对应的测试义务、证据维度和证据类型。

qcov scan 负责发现本地工件。它可以读取显式 @pytest.mark.qcov 标记、JUnit XML、coverage.py XML、Playwright JSON、LCOV 和生产观察记录,但它不会执行 pytest、Maven、Gradle、npm 或 Playwright。

扫描到报告或观察记录,不代表缺口已经消失。JUnit、Playwright 和生产观察工件都需要经过显式映射,才能物化为 QualityEvidence;coverage.py、LCOV 和 JaCoCo 仍然只是工件清单。它们可以帮助你知道哪里有数据,却不能单独证明某项业务义务。

这条边界有点严格,但它解决了一个更麻烦的问题:报告里明明有很多测试,为什么 gaps 仍然显示缺口?因为“有一份报告”和“这份报告证明了指定义务”本来就是两件事。

五分钟跑一遍 QCov

QCov 要求 Python 3.11+。QCov 本身是 Python CLI,即使被分析的项目使用 Java、TypeScript 或其他技术栈,也不需要把被测项目改造成 Python 项目。

git clone https://github.com/AI-Native-QA-Lab/QCov.git
cd QCov
python3 -m venv .venv
.venv/bin/python -m pip install -e '.[dev]'

.venv/bin/python -m qcov gaps \
  --obligation examples/refund/obligation.yaml \
  --evidence examples/refund/evidence \
  --locale zh-CN

这个 fixture 会刻意保留缺口,输出类似:

状态: `PARTIAL`
未证实维度: 并发, 幂等性, 生产环境

接下来可以按仓库的五分钟流程继续:

# 读取本地报告工件,不执行测试
.venv/bin/python -m qcov scan \
  --config examples/imported-reports/qcov.yaml

# 预览显式工件清单 → 证据映射
.venv/bin/python -m qcov map preview \
  --config examples/imported-reports/qcov.yaml \
  --format json

# 合并配置中的证据并重新评估缺口
.venv/bin/python -m qcov gaps \
  --config examples/imported-reports/qcov.yaml

这个顺序是有意设计的:先看已有缺口,再扫描工具产生的工件,然后预览映射,最后重新评估。每一步都能单独检查,出了问题也比较容易判断是测试义务、路径、工件清单、映射还是证据本身。

让变更影响和本地策略进入同一条工作流

QCov 不只看当前快照。对于已经提交的 Git 树,qcov diff 可以比较测试义务和证据快照;qcov impact 根据显式的变更路径映射找出受影响的义务;qcov affected 则只列出当前还没有 COVERED 的受影响义务。

以下命令假设被测项目已经准备好 qcov.yamlimpact.yaml,用于声明测试义务与变更路径映射;它是接入示例,不是克隆 QCov 后即可直接运行的命令。

.venv/bin/python -m qcov impact \
  --config qcov.yaml \
  --impact-config impact.yaml \
  --changed-file src/payments/refund.py \
  --format json

直接提供 --changed-file 时,QCov 可以报告受影响的测试义务,但不会假装已经算出缺口变化量。要比较两个本地 Git 快照,再使用 --base--head。它只读取 Git 对象,不会 fetch、切换提交、执行测试,也不会把未提交或未跟踪文件悄悄混进结果。

策略门禁也保持在本地:

.venv/bin/python -m qcov policy check \
  --config examples/imported-reports/qcov.yaml \
  --policy examples/refund/policy.yaml \
  --as-of 2026-09-07T00:00:00+08:00

policy check 只对已有的 coverage 状态做策略判断。允许的状态、精确的义务豁免和到期时间都写在策略文件里;策略输出 PASSWARNBLOCK,但不会修改原始 evidence,也不会把一个 PARTIAL 改写成 COVERED

仓库还提供 qcov obligation suggestqcov risk analyzeqcov plan 和 Agent helper。它们可以帮助整理需求、分析风险、排序下一步验证工作或解释缺口,但它们只是提案或助手输出,不是 QualityEvidence,也不是门禁权威。

QCov 可以接入 Python、Java 和 TypeScript 工具链

QCov 不要求所有团队使用同一个测试框架。不同工具的输出通过 Adapter 和显式映射接入,最终判断由统一协议完成。

项目入口QCov 读取什么证据边界
Python / pytest显式 @pytest.mark.qcov 标记、手写 QualityEvidence、coverage.py显式标记会生成 unknown 状态的证据,不会单独满足义务;有效的手写 QualityEvidence 才能参与评估;coverage.py 只是工件清单
Java / JUnit / JaCoCoJUnit XML、JaCoCo 工件清单选定的 JUnit 记录标识需要显式映射;JaCoCo 比率不能单独满足义务
TypeScript / PlaywrightPlaywright JSON、LCOV选定的 Playwright 记录标识需要显式映射;LCOV 只是工件清单
生产观察版本化的运行时、事故和可观测性工件生产观察需要显式映射,QCov 不直接访问远程可观测性平台

这就是 QCov 和传统覆盖率报告的分工。覆盖率报告告诉你哪些代码位置被执行过,QCov 进一步要求团队说明:这次执行和哪项需求、风险或不变量有关,它证明了哪个维度,还缺什么。

v1.0.0 已经交付了什么,也还没有承诺什么

QCov 的 v1.0.0 增加了确定性的 qcov impact / qcov affected、稳定的 qcov.impact/v1 输出契约、公开的 qcov.adapter/v1 Adapter SDK、JaCoCo 工件清单适配器,以及三类基于真实项目的案例输入。文档、协议和报告都提供中英文入口。

案例输入包的定位也需要说清楚:它们是脱敏、可复现的验证输入,保留元数据、测试义务、命令和映射边界,不是把外部项目复制进 QCov,也不是一份“所有项目都能自动得到完整覆盖”的证明。仓库案例文档 当前仍将三个 Release Gate 保持为 NOT_MET,因为发现的 false gap(误报缺口)还需要在修复后重新裁决。

这恰恰是项目应该公开说明的边界。QCov 的价值就在于把已证明、部分证明、缺失和未能判断的内容分开记录。代码质量检查通过、测试运行通过、案例输入可复现和业务 Release Gate 通过,也应当分别记录。

谁适合先用一轮

  • QA 和测试负责人:把需求、风险、测试结果和发布讨论放进同一份可追踪记录。
  • 使用 Coding Agent 的团队:检查 AI 生成或修改的测试到底绑定了什么证据。
  • 维护多语言项目的工程团队:用同一套测试义务和映射模型连接 pytest、JUnit、Playwright 等工具链。
  • 需要 PR 级风险提示的团队:在变更路径影响到业务义务时,先缩小回归和补证范围。

QCov 不适合用来替代测试运行器、覆盖率工具、安全扫描器或可观测性平台。它更像这些工具之上的一层证据整理和缺口判断——负责把“已经运行过什么”与“已经证明什么”分开。

从一个小义务开始

最快的方式,是先写一条真实的 TestingObligation,声明需要的证据维度,运行 qcov gaps,再把一个已经存在的 JUnit 或 Playwright 记录显式映射进去。这样很快就能看出哪些结果只是工件清单,哪些结果真的能改变缺口。

可以从 QCov GitHub 仓库 开始,再阅读 五分钟快速上手核心概念显式证据映射策略门禁

测试都通过了。很好。现在再问一句:它到底证明了什么?

分享