CI 全绿,但你的质量门禁可能已经悄悄少了一条规则

举报
ySimon 发表于 2026/09/18 14:40:34 2026/09/18
【摘要】 代码覆盖率测的是"哪些代码被执行到",测不出"哪些规则已经失效"。本文介绍一种把变异测试搬到规则文件上的做法:逐行删改自己的规则,重跑那条本该拒绝坏输入的命令,看哪些改动没有反应。作者拿它撞自己 25 条规则的校验门禁,得到 175 个变异、78 个漏过、其中 4 类真缺陷——包括一条"触发条件由被检方自己提供"的设计缺陷。附可复现命令。

一、CI 绿灯,说明不了什么

先看一个很常见的组合:

  • 你的项目里有 schema.json、有一份 lint 配置、有一个 validate.py、有一道 LLM guardrail;
  • 流水线里挂着一条命令,坏输入进来它就返回非零,构建变红;
  • 这条命令一直是绿的。

“一直是绿的"通常被读成"规则在生效”。但它其实只说明最近没有人往里面送坏输入。

这两件事的差别,在规则悄悄失效的时候才会显形——而你恰恰不会知道。规则失效的典型原因不是有人删掉了它,而是它从来就没被验证过:加规则的时候顺手复制了上一行、条件写反了、字段改名之后引用断了、某条分支永远进不去。代码没报错,CI 没变红,你也没有理由去查。

于是你带着一份自以为在保护你的规则集上线了。

二、代码覆盖率为什么盖不住这个

覆盖率回答的问题是:哪些代码被执行过。

它不回答:哪些规则真的在起作用。

一条写错的规则,会被覆盖率当成"已覆盖"——因为它所在的函数确实被调用了,只是它在任何输入下都不返回拒绝。覆盖率给的是执行痕迹,不是判定能力。

真正能回答第二个问题的,是变异测试(mutation testing)的思路:

故意把被测对象改坏。如果测试还能过,说明测试没测到这块。

传统变异测试的靶子是代码。但规则文件、策略配置、schema、guardrail——这些东西同样是"逻辑",同样会失效,却几乎没有人对它们做变异。

三、做法:把变异打在规则文件上

思路很直白:

  1. 给你一份规则文件(JSON Schema / CI 检查 / lint 配置 / 策略文件 / guardrail);
  2. 给你那条"返回非零即拒绝"的命令;
  3. 工具一行一行地删改规则文件,每改一次就重跑一次那条命令;
  4. 报告哪些改动没有被拦下来。

任何一个"活下来"的变异,都对应一条没有任何东西在测试的规则。

举例:如果删掉 "required": ["name"] 之后,校验命令依然返回 0(通过),那说明"name 必填"这条规则,在你的流水线里其实是空转的——它拦不住任何东西。

四、30 秒复现

它只用 Python 标准库,没有依赖、不需要凭据、不启动任何服务:

git clone https://github.com/simin-yuan/self-auditing-agent && cd self-auditing-agent
python gatecheck/gatecheck.py \
  --gate "python examples/gate.py {target}" \
  --target examples/service-config

两个参数的含义:

  • --gate:那条本该拒绝坏输入的命令。任何"返回非零即拒绝"的东西都能当门禁——linter、CI 步骤、schema 校验器、你自己写的 validate.py;
  • --target:规则和样本所在的目录。

有一个使用上的坑要点明:门禁命令要写在 --target 外面。 否则工具会把变异打在门禁自己身上,测出来的东西就不是你要的了。

退出码是 0/1,所以它能直接当作流水线里的一个步骤——包括华为云 CodeArts 流水线或任何 CI。零依赖意味着你不需要为了加它而引入一套测试框架。

五、把它对准自己的门禁:175 / 78 / 4

工具好不好用,看它能不能撞出真东西。我拿它对准了我自己那套 25 条规则的校验门禁:

指标 数字
生成的变异总数 175
没被拦住的变异(漏过) 78
逐条复查后确认的真实缺陷 4 类

78 个漏过不等于 78 个缺陷。 这一点必须说清楚:里面大部分是无害的——删掉一个 title 字段、删掉一个可选属性,本来就不该影响判定。工具的价值在于把候选缩小到你要人工看的范围,不在于替你下结论。

真正值得看的是那 4 类。

六、最值得看的那一条

四条里最狠的,是一个设计层面的缺陷,不是写错的语法:

有一条规则的触发条件是——“如果你声明自己是非交互功能,你就必须登记”。

问题在于:这个"声明",是被检方自己提供的。

于是攻击路径变得极其简单:把声明删掉,那条要求就消失了。

这条规则在正常路径上工作得很好,文档里读起来也完全合理。它失效的方式是结构性的:

一个只在你自认有罪时才生效的检查,等于没有检查。

(同源的坑在很多地方都能见到:自评表、自查清单、“如涉及 X 请勾选”、由被评估方自己划定评估范围。判断一个检查有没有效,要先问一句——它生效的前提,是谁提供的?)

七、两条设计原则

这套东西里我认为最通用的,不是那个工具,是两条原则:

原则一:一个不能输出"否定"的判据,不是判据。

只会报"通过"的校验器比没有校验器更糟——它给了你信心,但没给你保护。

原则二:只会报"失败"的判据,同样没用。

因为那时你分不清"这是一个严格的检查"还是"这个检查坏掉了"。

所以正确的做法是双向断言:既证明它在坏输入上会拒绝,也证明它在好输入上会通过。任何一边塌了,这个判据就作废。

我的仓库里就是这么做的——两条命令、每次推送都在干净机器上重跑一遍,任一条不成立徽章就变红。主张必须能被复现,否则它只是主张。

八、这套东西的边界(说在前面)

诚实一点,gatecheck 目前的限制是明确的:

  • 它只给清单,不给判决。 活下来的变异是候选,不是缺陷。谁要是喊"发现 19 个缺陷",等你第二次核对发现大部分是无害的,你就不会再信它了——工具的可信度是这么被消耗掉的;
  • 变异算子只有 7 个,覆盖面有限:目前只做行级的删除/损坏,没有做值级别、类型级别、跨文件的语义变异。所以"没发现漏过"≠“规则没问题”;
  • 它测的是规则文件的判定能力,不测规则本身是否符合你的业务意图。一条被完整测试的错规则,依然是错规则。

九、怎么用在你自己的项目上

如果你的项目里有下面任何一样东西,都值得跑一遍:

  • 一份 JSON Schema 或 OpenAPI 定义;
  • 一道 CI 里的策略检查 / 准入检查;
  • 一份 lint 或安全策略配置;
  • 一条 LLM 的输出 guardrail。

上手顺序建议:先跑一次拿到清单 → 挑里面你以为是硬约束的那几条 → 手工确认哪些是真缺陷 → 先修"触发条件由被检方提供"这一类结构性问题,再修字段级的。

最后留一个问题,比工具本身更值得带走:

你现在的门禁里,有哪几条规则,是从来没有人试过把它弄坏的?


参考:仓库 github.com/simin-yuan/self-auditing-agent(MIT 许可,Python ≥3.9,零依赖);未修复缺陷的完整诊断见仓库内 docs/BLIND-SPOTS.md。

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

评论(0)

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

全部回复

上滑加载中

设置昵称

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

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

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