报告里写『通过率 92%』,等于只报了一半:把 AI 测试数字连置信区间一起交出去

举报
霍格沃兹测试学社 发表于 2026/09/28 18:06:35 2026/09/28
【摘要】 同一套回归用例,周一早上跑完是 88%,你不放心又跑了一次,变成 94%。周报你贴了 94%——贴 88% 那次,评审会上要被人追问『怎么又掉了』;贴 94% 这次,没人记得它统共就跑了十七八条。数字没撒谎,你只是从一摞结果里挑了最顺眼的那一颗珠子。AI 应用的通过率、稳定性,天生是一个分布,不是一个点。模型输出的随机性、用例里那一两条边界抖动,都会让『同一次改动』每次跑出来的通过率在一个带...

同一套回归用例,周一早上跑完是 88%,你不放心又跑了一次,变成 94%。周报你贴了 94%——贴 88% 那次,评审会上要被人追问『怎么又掉了』;贴 94% 这次,没人记得它统共就跑了十七八条。数字没撒谎,你只是从一摞结果里挑了最顺眼的那一颗珠子。

AI 应用的通过率、稳定性,天生是一个分布,不是一个点。模型输出的随机性、用例里那一两条边界抖动,都会让『同一次改动』每次跑出来的通过率在一个带子里晃。报告只交一个点,是系统性地把『我其实没那么确定』这件事抹平了。正确做法,是把每个关键数字连它的不确定度一起交出去。

一、把区间交出去:通过率 X%,95%CI 也要一起写

先说最小动作。你手上有一组通过记录——每条用例跑过记 1、没过记 0,或者你把多次运行的通过率攒成一列。别只算平均,用 bootstrap 从这组记录里有放回地重采样几千次,每次算一个通过率,把这一堆通过率排序,取中间 95% 的两个端点,就是置信区间。报告里那行字,于是从『通过率 92%』变成『通过率 92%,95%CI [86%, 95%],n=210』。读者第一次能看见这个数字到底有多『实』。

呈现口径 读者会得出什么结论 什么时候会误导
裸点估计『通过率 92%』 系统稳定在 92%,可以直接跟 95% 的放行线比 n 很小或多次波动时:一个数盖住了它其实只跑了十几条
点 + 区间『92%,95%CI [86%, 95%],n=210』 真实值大概率落在这个带里,够不够格卡线一眼能判 几乎不误导;只需提醒一句:区间说的是『当期真值』,不是『下次跑分落点』
两版区间重叠比较 两次到底是不是『真的不一样』 只报两个点就下『显著提升 / 明显退化』:两个区间其实大面积重叠

bootstrap 是公开统计方法,不用连任何外部服务,纯标准库就能跑——这也正是它适合长在流水线里的原因。

还有一层用 bootstrap 而不是背公式的理由:通过/失败是 0/1 数据,当真值偏向九成以上时,抽样分布明显左偏,用『±1.96×标准误』那套正态近似算出来的区间,在小样本下会越过 100%、甚至越过 0,覆盖率一塌糊涂。bootstrap 不假设正态,它把样本当成总体本身,重采样几千次,区间长成什么样完全跟着数据走——样本越小,它就越宽,这个『宽』恰恰是报告里最该被看见的诚实。

二、区间宽度本身就是一条质量结论

把那套十七八条的用例塞进脚本,跑出来是这个样子:周一那次『通过率 88%,95%CI [71%, 100%]』,重跑那次『通过率 94%,95%CI [82%, 100%]』。6 个点的差距,背后只是两条用例翻了个面——因为 n=17,一条用例的值就差不多占 6 个百分点,点估计当然站不稳。区间宽到 29 个点,等于报告在喊:这次样本太少,不足以单独支撑放行。这是一条过去被『92%』这种漂亮数字盖住的结论。

再换一套:同一套逻辑扩到 210 条,通过率还是 91%,可区间收紧到 [88%, 95%],宽度只剩 7 个点。同样是『九成通过』,一个宽到不敢签字、一个够到能拿去和 90% 的放行线比。宽度不是噪音,宽度是你本该写进报告的第二条结论。

顺带堵一个常被误读的地方:置信区间说的是『这批改动真实通过率大概率藏在哪』,不是『下一次再跑,通过率会落在哪』。前者是关于真值、关于『我有多确定』,后者是关于单次跑分的波动。报告里把区间按真值的意思写,它才能拿去当放行依据;一旦读者把它错当成下次跑分的预言,那句『95%CI [88%, 95%]』就会被理解成『下次跑一定在 88 到 95 之间』——那就不是同一件事了,卡线的锅也说不清。

【配图1插入位置:本小节之后】

三、比较两个数字,先看区间重不重叠

『新版本通过率 93%,旧版本 90%,提升 3 个点,建议升级』——这种话在报告里出现一次,就该被拦一次。把两次各 n=100 的结果丢进脚本:旧版 90% 落在 [84%, 95%],新版 93% 落在 [88%, 97%],两个区间重叠了将近八成。重叠到这个份上,3 个点的差完全在样本波动能解释的范围内,写『显著提升』就是把运气写成了能力。规矩很简单:新旧两个区间大幅重叠,就老实写『本次差异落在噪声范围内』;只有区间基本分离,才配得上『显著』这两个字。

反过来,当两个区间确实基本分离——比如新版下压到了旧版上界之上——那才敢理直气壮写『显著提升』,而且能顺带写清是怎么判的:一句『两次 95% 区间不重叠』,就能挡住所有『你这是不是运气』的口头争执。『显著』『明显』这类词,在报告里永远不该脱离动作单独出现;出现了,就得能回答『拿什么判的显著』。

四、报告里这几个字段,得并排站

区间不是孤立的一列,它得和几个字段捆在一起才读得懂。单看一个 [88%, 95%] 没意义,得知道它是 210 条跑出来的、得知道分母口径是同一批用例、得知道是哪一次运行。

字段 演示值 为什么必须并排出现
通过率(点估计) 91% 给个体感,但单独看会骗人
样本量 n 210 决定区间宽度的根,没了它区间无从算起
95% 置信区间 [88%, 95%] 点估计的可信带,能不能卡线看它
口径 同一用例集、同一配置 防拿不同分母、不同配置的数字横比
运行时间 2026-09-24 01:12 · #4471 防拿旧一次的数冒充这一次

口径和运行时间这两栏看着像摆设,其实卡的是最常见的翻车:把上次大样本的区间宽度,套到这次小样本的通过率上,或者拿三个月前那次的漂亮数字给今天背书。

五、跑一遍:纯标准库把区间算出来

下面这段只用标准库,喂进去的就是本文演示构造的通过记录,开箱即跑。接自家工程时,把 records 换成你 pytest 结果里每条用例的 0/1,或多次运行攒出的通过率列表即可。

# -*- coding: utf-8 -*-
"""pass_rate_interval.py —— 通过率连置信区间一起交(纯标准库,开箱即跑)

用法:python pass_rate_interval.py
输入一组 0/1 通过记录,bootstrap 重采样算 95% 置信区间,
输出报告用文案『通过率 X%,95%CI [a%, b%],n=Y』。数据均为演示构造。
"""
import random


def point_estimate(records):
    return sum(records) / len(records)


def bootstrap_ci(records, iters=10000, level=0.95, seed=20260924):
    """对 0/1 记录做 bootstrap:有放回重采样 n 次、每次算一个通过率,
    把通过率分布排序后取分位数当置信区间(percentile 法)。"""
    rng = random.Random(seed)
    n = len(records)
    means = sorted(sum(rng.choices(records, k=n)) / n for _ in range(iters))
    lo = int((1 - level) / 2 * iters)
    hi = int((1 + level) / 2 * iters) - 1
    return means[lo], means[hi]


def fmt_pct(x):
    return f"{x * 100:.0f}%"


def report_line(records, iters=10000):
    p = point_estimate(records)
    lo, hi = bootstrap_ci(records, iters=iters)
    return f"通过率 {fmt_pct(p)},95%CI [{fmt_pct(lo)}, {fmt_pct(hi)}],n={len(records)}", hi - lo


def overlap(a, b):
    """重叠宽度占较窄区间的比例:>0.5 视为『分不出胜负』,别写显著提升。"""
    inter = max(0.0, min(a[1], b[1]) - max(a[0], b[0]))
    narrowest = min(a[1] - a[0], b[1] - b[0])
    return inter / narrowest if narrowest else 1.0


def gen_demo_passes(n_true, n_total, seed):
    rng = random.Random(seed)
    recs = [1] * n_true + [0] * (n_total - n_true)
    rng.shuffle(recs)
    return recs


def main():
    print("== 场景A:同一套用例,样本很小,点估计站不稳 ==")
    for lab, rec in [("周一那次", gen_demo_passes(15, 17, seed=1)),
                     ("手快重跑那次", gen_demo_passes(16, 17, seed=2))]:
        line, width = report_line(rec)
        print(f"  {lab}:{line}(区间宽度 {fmt_pct(width)})")

    print("\n== 场景B:同一套逻辑扩到上百条,通过率没动,区间却收窄 ==")
    line2, width2 = report_line(gen_demo_passes(192, 210, seed=3))
    print(f"  {line2}(区间宽度 {fmt_pct(width2)})")

    print("\n== 场景C:新旧两版通过率,先过区间重叠关 ==")
    old, new = gen_demo_passes(90, 100, seed=4), gen_demo_passes(93, 100, seed=5)
    lo_old, hi_old = bootstrap_ci(old)
    lo_new, hi_new = bootstrap_ci(new)
    print(f"  旧:{report_line(old)[0]}")
    print(f"  新:{report_line(new)[0]}")
    ov = overlap((lo_old, hi_old), (lo_new, hi_new))
    verdict = "两区间大幅重叠,别写『显著提升』" if ov > 0.5 else "区间基本分离,可写『显著提升』"
    print(f"  重叠比例 {ov * 100:.0f}% → {verdict}")


if __name__ == "__main__":
    main()

两个刻意的处理值得点名。其一,bootstrap 固定了随机种子(seed=20260924),这样这份报告里的区间下次重算还是同一个——用随机方法去算一份要进报告的确定性数字,得先把重采样这层随机按住在报告里,不然『区间』本身又成了新的一颗珠子。其二,overlap 判的是『重叠宽度占较窄区间的比例』,不是简单看有没有交集;只要沾一点边就算分开太松,会把噪声差判成显著。脚本跑出来的三组数,就是前面几节引用的全部数字:n=17 两条宽到 [71%, 100%]、[82%, 100%],n=210 收到 [88%, 95%],新旧对比重叠 78% 判成『别写显著提升』。

六、挂进流水线:报告正文一行带区间

区间化这件事最好长在流水线里,否则又变成『想起来才做』。pytest 出结果之后加一步统计,直接写进 step summary:

- run: pytest --junitxml=report.xml
- run: python pass_rate_interval.py >> $GITHUB_STEP_SUMMARY

从此报告里那句通过率,天生带着它的区间和 n。谁想只贴那颗最顺眼的珠子,脚本第一行就把区间甩在他脸上。

写这行报告文案时,给个小的语序建议:先点估计给体感,再区间给幅度,最后 n 给底气——『通过率 91%(95%CI 88%–95%,n=210)』。别把区间放在最前面,读者抓不到锚;也别把 n 省在文末脚注里,忙的人根本不会往下翻,而 n 恰恰是那个能一票否决整句结论的数字。

七、回到那场评审会

下次有人拿你周报里那个漂亮的百分比对上『为什么又掉了』,答案不再是运气:那个数一直是带,不是点;宽的时候本就不该拿去签字,窄的时候才有资格和放行线比。把区间交出去,不是把报告写复杂,是终于把『我有多确定』这件一直在心里、却从没写下来的事,写下来了。

诚实的质量报告,报的不是一个通过率,是一整个『这个数大概落在哪』——只交点估计,等于把确定性的成本悄悄转嫁给了读报告的人。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。