ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

覆盖率 92% 仍然漏掉故障:用代码智能体、属性测试与变异分析建立反例工厂

覆盖率 92% 仍然漏掉故障:用代码智能体、属性测试与变异分析建立反例工厂

一次结算服务改造后,团队把单元测试覆盖率从 71% 提到了 92%。新增测试大多由代码智能体生成:正常订单、空订单、折扣订单、不同税率订单都有断言,CI 连续两周保持全绿。上线第三天,一笔“先退款、后补记汇率”的跨币种订单触发重复舍入,账面与支付渠道相差 0.01 元。测试确实执行了出错的函数,也覆盖了异常分支,却没有表达最关键的不变量:无论事件以什么合法顺序到达,最终入账金额必须守恒。

这类问题暴露了一个常见误区:把 AI 生成测试理解成“根据函数签名批量补几个 example”。示例测试擅长保护已知路径,却很难主动寻找开发者没有想到的组合。模型生成的断言如果只是复制实现结果,甚至会把错误逻辑固化成新的“预期行为”。覆盖率继续上升,团队得到的可能只是更密集的绿色信号,而不是更强的缺陷发现能力。

我更倾向把代码智能体放进一座“反例工厂”。人负责定义业务不变量、风险边界和可接受的输入域;属性测试引擎负责大规模生成组合;差分执行和变形关系提供不依赖单一实现的判断依据;变异测试主动向代码注入小错误,检验测试能否将它们杀死;模型分析幸存变异体、提出新属性并解释最小失败样本,但不能自行宣布行为正确。

本文选择的写作参数是 IT 研发效能与代码智能体,技术实体包括 Python Hypothesis、pytest、Mutmut、Qwen2.5-Coder、DeepSeek-Coder、CI/CD、差分测试、变形测试和失败用例缩减。创源AIGC只作为可替换的模型联调节点出现,测试事实、执行沙箱、通过标准与发布责任仍保留在团队自己的工程系统中。

一、先拆穿绿色覆盖率:测试执行过,不等于测试验证过

行覆盖率回答的是“某一行是否被执行”,分支覆盖率回答的是“真假分支是否都走过”,它们都不回答“断言能否识别错误结果”。下面这个舍入函数可能拥有完整的行覆盖和分支覆盖:

fromdecimalimportDecimal,ROUND_HALF_UPdefsettle(amount:Decimal,rate:Decimal,refunded:bool)->Decimal:gross=(amount*rate).quantize(Decimal("0.01"),ROUND_HALF_UP)ifrefunded:return-grossreturngross

如果测试只检查settle(Decimal("100"), Decimal("1"), False) == Decimal("100"),即使有人把汇率乘法改成加法,覆盖率仍然不会变化。更隐蔽的是 snapshot 断言:测试先运行当前实现,把输出保存为快照,之后只比较是否与快照一致。若初始实现已经错误,快照只是把错误复制到仓库里。

测试质量至少由三个维度组成。第一是可观察性:测试是否能看到状态、副作用、异常类型和外部交互;第二是判别力:错误实现是否会让测试失败;第三是输入探索力:测试能否覆盖边界组合、事件顺序和跨字段约束。覆盖率只触及第一维的一部分,不能替代后两个维度。

代码智能体特别容易制造“弱断言”。它看到函数返回列表,便断言结果是列表;看到接口返回 200,便断言状态码为 200;看到实现对金额保留两位小数,便复制同样的公式计算 expected。这样的测试语法正确、运行稳定,也能增加覆盖率,但没有建立独立于实现的判断标准。测试与被测代码共享同一个错误假设时,二者会一起通过。

因此,反例工厂的第一个指标不是生成了多少测试,而是新增测试杀死了多少已知错误。可以从历史缺陷、回滚记录和人工构造的微小变异开始:把>改成>=、移除一次校验、交换事件顺序、把 Decimal 换成 float、删除幂等键检查。若新增测试对这些变化没有反应,就不应因为覆盖率提高而获得发布信用。

还要区分“未覆盖代码”和“不可判定行为”。未覆盖代码可以通过补输入解决;不可判定行为缺的是 Oracle,也就是判断正确与否的依据。例如推荐排序很难给出唯一正确列表,但仍可以验证屏蔽商品不会出现、同分商品排序稳定、加入无关候选不会改变头部结果。把问题从精确输出改写成必须成立的关系,测试空间才会真正打开。

团队可以为每个模块维护风险画像:金额计算关注守恒、精度和顺序;权限模块关注默认拒绝、角色单调性和租户隔离;缓存模块关注一致性、过期和击穿;解析器关注往返、拒绝非法输入和资源上限。模型生成测试前先读取风险画像,而不是直接读取全部源码后自由发挥。这样生成结果会围绕故障模式,而不是围绕函数表面形状。

二、行为契约不是示例清单:把业务规则写成可执行不变量

属性测试的起点不是随机数,而是可执行的行为契约。不变量描述在一类输入或状态转换下始终应该成立的条件。对结算服务,常见不变量包括金额守恒、同一幂等键最多产生一次入账、退款不超过原交易金额、币种转换前后误差不超过最小货币单位,以及事件重放不会改变最终余额。

好的不变量同时具备业务含义和失败可解释性。“结果不为 None”过于宽泛,“任意已确认订单经过 settle 后,借方总额与贷方总额之差为零”才具有判别力。写属性时需要显式指出输入域、前置条件、转换动作、期望关系和允许误差。任何隐藏在测试夹具里的默认值,都会缩小模型和引擎真正探索的空间。

可以把契约分为四层。值域属性约束单次输出,例如金额不得出现 NaN;代数属性验证运算关系,例如合并顺序不影响总额;状态机属性验证事件序列,例如 canceled 之后不能回到 paid;资源属性限制时间、内存和外部调用次数。四层共同存在时,才能覆盖“结果正确但资源失控”或“单步正确但序列错误”的故障。

对既有系统,不变量不一定写在需求文档里。可以从数据库约束、审计报表、对账 SQL、历史事故和客服补偿规则中提取。代码智能体可以帮助归纳候选规则,但每条规则必须由领域负责人确认来源。例如模型发现“退款金额似乎总小于原金额”,工程师需要判断这是业务规定、当前数据偏差,还是历史实现限制。未经确认的统计规律不能升级为发布门禁。

不变量还要带版本。促销规则、税率计算和状态机可能按地区或日期变化,contract_version应与业务配置版本一起进入测试证据。旧订单按旧契约重放,新订单按新契约验证。若只保留一套“当前规则”,历史样本会在规则升级后大量失败,团队很快就会选择忽略属性测试。

一个可维护的契约对象可以包含以下字段:

contract_id:ledger-balance-conservationversion:3applies_when:currency:[CNY,USD,EUR]order_state:[confirmed,refunded]invariant:debit_total == credit_totaltolerance:minor_units:0evidence_sources:-finance-rule-17-incident-2026-04-roundingowner:settlement-domain

模型接收的不是一句“多写边界测试”,而是契约、输入模式和历史失败分类。它可以提出“事件重排后余额是否仍守恒”“拆分付款再合并是否等价”等候选属性,并说明每个属性对应哪个风险。候选属性只有在人工确认、确定性工具执行且能够稳定复现后,才进入主干测试。

契约也必须允许无法验证。有些属性需要真实支付渠道、硬件时钟或合规数据,在普通 CI 中无法得到可信结论。这时应将验证级别标为 unit、simulation、staging 或 production-observation,而不是用 Mock 假装已经验证。Mock 只能证明调用协议符合预期,不能证明第三方系统的真实语义。

三、让生成器理解输入域:属性测试不是盲目随机轰炸

属性测试引擎的核心能力是根据策略生成大量输入,并在失败后自动缩减。真正困难的部分是输入域建模。如果让引擎随机生成任意字符串和数字,大多数样本会在参数校验阶段被拒绝,既浪费时间,也无法触达深层业务逻辑。生成器应产生“合法但刁钻”的对象,同时保留一部分专门验证拒绝路径的非法对象。

以跨币种订单为例,输入字段之间存在约束:退款金额不超过支付金额,汇率精度与币种相关,事件时间不能早于订单创建时间,同一幂等键的重复事件内容应一致。把每个字段独立随机生成,会制造大量业务上不可能的组合。Hypothesis 的组合策略可以先生成订单,再基于订单生成相关事件:

fromdataclassesimportdataclassfromdecimalimportDecimalfromhypothesisimportgiven,strategiesasst@dataclass(frozen=True)classPayment:amount:Decimal rate:Decimal refunded:boolamounts=st.decimals(min_value=Decimal("0.01"),max_value=Decimal("1000000.00"),places=2,allow_nan=False,allow_infinity=False,)rates=st.decimals(min_value=Decimal("0.0001"),max_value=Decimal("1000.0000"),places=4,allow_nan=False,allow_infinity=False,)payments=st.builds(Payment,amounts,rates,st.booleans())@given(payments)deftest_refund_is_additive_inverse(payment:Payment)->None:charged=settle(payment.amount,payment.rate,False)refunded=settle(payment.amount,payment.rate,True)assertcharged+refunded==Decimal("0.00")

这段测试没有枚举具体金额,而是验证“退款是扣款的加法逆元”。当某个舍入、符号或极值组合破坏属性时,引擎会把输入缩减成最小失败样本。最小样本比一长串随机数据更适合进入缺陷单,因为工程师能直接看出触发条件。

生成器需要显式分层。基础层生成单字段边界,例如零、最小货币单位、最大长度和 Unicode;对象层维护字段间约束;序列层生成状态转换和重复事件;拓扑层生成服务依赖、权限继承或图结构。模型可以帮助为某一层补充策略,但不能把非法前置条件用assume大量过滤。过滤比例过高意味着输入模型有问题,应重写生成器。

生产样本可以用于校准分布,却不应直接成为唯一数据源。只按线上频率采样,罕见币种、极端金额和异常顺序会继续缺席。更稳妥的方法是将输入分为真实分布、风险分布和均匀探索三部分:真实分布验证常见路径,风险分布放大历史故障附近的样本,均匀探索寻找未知边界。三部分分别统计缺陷产出,避免热门路径吞掉全部测试预算。

随机种子、策略版本和测试预算必须写入证据。失败时保存 seed、最小样本、引擎版本、契约版本和源码提交。CI 重跑首先使用已保存样本,再运行新探索;这样既能稳定复现,也不会把属性测试退化成固定回归集。若同一失败只能偶尔出现,应先标记为 flaky-investigation,而不是自动重试到绿色。

代码智能体生成策略时还要接受复杂度限制。递归结构设置最大深度,字符串设置最大长度,状态序列设置步数和非法动作比例,外部调用由内存实现或受控沙箱替代。否则一次“更全面”的策略修改可能把 CI 时间从十分钟拖到两小时,最终迫使团队关闭整个测试层。

四、没有标准答案时,用变形关系与差分执行制造 Oracle

许多复杂函数没有廉价的精确答案。搜索排序、编译优化、图像处理、代码格式化和大模型输出都可能存在多个合理结果。若测试坚持逐字比较固定输出,要么脆弱到每次升级都失败,要么宽松到只检查“没有报错”。变形测试提供了第三条路:不要求知道某次输出应该是什么,只要求输入发生特定变化后,输出之间满足可解释的关系。

以汇率结算为例,将一笔无折扣付款拆成若干笔后,总额应在允许的最小货币误差内保持一致;对权限判断器,给角色增加无关的只读权限不应撤销已有读取能力;对排序器,加入一个分数低于所有候选的元素,不应改变原有头部顺序。这些都属于变形关系。它们来自领域规则,不依赖当前实现,因此比复制函数公式更能识别错误。

变形关系可以表示为 source_input、transform、relation 和 tolerance。代码智能体读取接口和风险画像后提出候选 transform,例如重复、重排、拆分、合并、编码往返、加入中性元素或缩放;领域负责人确认该变换在业务上合法;确定性执行器生成源输入与派生输入;关系检查器比较结果。模型不直接判断两个复杂输出“语义上差不多”,因为这种判断不可稳定回放。

差分测试则把两个相互独立的实现当作参照。新旧版本、Python 与 Java 实现、优化器开关前后、内部服务与第三方渠道,都可以接收同一组生成输入。差异并不自动意味着新版本错误,但会生成一个需要分类的 counterexample。分类结果包括 expected_change、legacy_bug、new_regression、undefined_behavior 和 oracle_gap。

差分执行最容易踩的坑是把旧系统当作绝对真相。遗留实现可能已经包含多年未发现的缺陷;如果新实现修复了它,简单的“输出必须一致”反而会阻止修复。解决办法是先比较结构化语义,再用不变量裁决。例如两个版本的日志文本可以不同,但借贷守恒、状态终态和外部副作用必须一致。无法用契约裁决的差异进入人工队列,而不是自动失败或自动接受。

副作用也要进入差分范围。对订单处理器,不能只比较返回 JSON,还要比较数据库写集合、消息主题、幂等键、调用次数和事件顺序。可以在沙箱里将外部接口替换为记录型适配器,把副作用归一化成 Effect Log。动态时间戳和随机 ID 使用逻辑占位符处理,但业务金额、租户和动作类型不能被清洗掉。

模型适合分析差异簇,而不是逐条阅读百万个输出。平台先按字段路径、异常类型和 Effect Log 指纹聚类,再让模型为每个簇生成候选解释与补充检查。任何“预期变化”的标记都需要关联需求或契约版本。没有证据的“看起来合理”不能进入忽略列表,否则差分测试会逐渐积累一个无人理解的豁免仓库。

变形关系本身也要被测试。可以故意实现一个明显错误的版本,检查关系是否能发现;可以对 transform 做逆变换,验证是否回到等价输入;还可以统计每条关系触达的状态和杀死的变异体。长期没有发现差异、没有覆盖风险状态、也没有杀死变异体的关系,应被审查或移除,而不是因为名字专业就永久保留。

五、主动破坏代码:用变异测试判断测试是否真的有牙齿

变异测试会对生产代码做小幅、可控的修改,再运行测试观察是否失败。典型变异包括反转条件、修改边界、删除返回值、替换算术运算、移除异常、改变常量和跳过函数调用。若测试失败,变异体被杀死;若测试仍通过,说明存在测试盲区、等价变异或无效代码。

变异分数不是越高越好。某些变异不会改变可观察行为,例如把两个等价表达式互换;某些代码属于防御分支,在普通环境难以触发;还有些变异只影响日志文本。团队应把变异体按业务风险分层:账务、权限和数据删除属于高风险,必须逐个解释幸存原因;格式化、诊断日志可以使用较宽阈值。一个全局 90% 指标会掩盖关键模块的幸存错误。

建议把变异执行拆成两级。Pull Request 阶段只运行与改动相关的增量变异,控制在可接受的十分钟预算内;夜间流水线运行完整矩阵,并把新幸存变异体分配给模块负责人。基线中已有的幸存体不能让新 PR 失败,但数量不能增长;对高风险目录,可以要求新增代码没有未解释幸存体。

下面是一份偏策略化的配置示例:

mutation_gate:changed_files_only:truetimeout_multiplier:3risk_profiles:-path:"src/ledger/**"minimum_kill_rate:0.95unexplained_survivors:0-path:"src/permission/**"minimum_kill_rate:0.98unexplained_survivors:0-path:"src/presentation/**"minimum_kill_rate:0.75unexplained_survivors:5

代码智能体处理的是“为什么活下来”。它可以读取变异位置、相关测试、覆盖路径和契约,提出三类建议:新增一个更强属性、扩大生成器输入域、删除不可达代码。模型不能直接把变异标记为 equivalent,因为等价性通常需要符号推导或人工判断。若建议只是为变异常量写一个特定示例,审核者要警惕测试被工具牵着走。

一个有效流程是从幸存体反推测试缺口。假设把refund <= paid变成refund < paid后测试仍通过,说明生成器从未产生全额退款;修复方式不是只添加paid=100, refund=100,而是把“全额退款是合法边界”写进对象策略和状态机属性。这样未来金额、币种和事件顺序变化时仍会探索该边界。

变异测试还可以校准 AI 生成测试的价值。记录每个生成测试新增杀死的变异体、覆盖的新契约、运行时长和稳定性。如果某批测试只增加行数和覆盖率,没有杀死任何既有或新变异体,就进入人工抽查而不是自动合并。删除这些弱测试后,如果门禁能力没有下降,说明它们原本只是维护负担。

性能问题需要独立处理。变异工具会重复运行测试,慢速集成测试可能造成指数级膨胀。可以先用覆盖映射选择受影响测试,再按变异算子并行执行;数据库与网络场景使用确定性沙箱;超时结果标为 timed_out,不能算作 killed。把超时当作杀死会鼓励团队保留不稳定、缓慢的测试。

六、模型只提交测试提案:中继、上下文与执行权限彻底分层

代码智能体要生成有价值的属性,需要读取接口、契约、历史缺陷和部分源码,但不需要访问生产数据、发布凭据或任意 Shell。合理的架构分为上下文编译器、模型中继、提案校验器和执行沙箱。上下文编译器负责选择最小必要材料;中继只做协议适配和模型调用;校验器解析结构化提案;沙箱运行确定性测试工具。

模型提案应包含 property_id、risk_ref、target_symbols、generator_change、assertion_relation、expected_failure_mode 和 confidence_note。提案中的代码只是候选补丁,必须经过格式化、静态分析、依赖白名单和人工审核。模型不能直接修改主干,不能更新覆盖率基线,也不能把幸存变异体加入豁免。

研发联调可以把模型节点和安全策略分开配置:

test_agent:protocol:openai-compatiblebase_url:https://178.nz/yinc/v1api_key_env:TEST_AGENT_TOKENmodel_alias:property-test-reviewcontext_profile:source-and-redacted-incidentstool_calls:disabledoutput_schema:test-proposal-v2timeout_seconds:50

这里的节点可以替换为本地模型、开源项目、云厂商或创源AIGC,但替换不应改变执行权限。Qwen2.5-Coder、DeepSeek-Coder 等代码模型只返回文本或结构化提案,pytest、Hypothesis 和 Mutmut 在本地受控环境运行。即使模型返回一段删除文件或连接数据库的命令,提案校验器也不会将其转换为工具调用。

上下文编译器要防止“全仓库打包”。它从变异位置向外提取调用签名、类型、相关契约、现有测试和历史失败摘要,敏感配置、密钥、客户数据和无关模块默认排除。每份上下文保存 manifest,记录文件哈希、截取范围和脱敏规则。后续比较模型版本时,只有输入 manifest 相同,结果才具有可比性。

模型输出必须引用风险或证据。它提出“增加空字符串测试”时,需要说明对应的是解析器契约、历史缺陷还是某个幸存变异体;没有引用的泛化建议不进入自动候选队列。引用存在也不代表建议正确,校验器还要确认符号、字段和契约版本真实存在,防止模型生成看似合理但仓库中没有的接口。

多模型并不意味着简单投票。一个模型可以负责从契约生成属性,另一个模型负责寻找断言与实现的共同假设,确定性工具负责执行和变异评分。若两个模型都基于同一段错误源码,它们仍可能一致地犯错。真正独立的信号来自业务契约、历史事故、差分实现和变异结果,而不是模型数量。

模型调用日志只保存必要元数据:任务 ID、模型别名、上下文 manifest、输出哈希、延迟、Token 数和解析状态。原始源码与事故摘要按仓库权限保存,不复制到普通运营日志。若模型服务不可用,反例工厂仍可运行已有属性、回归样本和变异门禁;缺失的只是新提案,不应阻塞正常测试事实的生成。

接入价值要用缺陷发现效率衡量。比较人工与模型提案在单位审核时间内新增的有效属性、杀死的高风险变异体、发现的真实缺陷和引入的 flaky 测试。模型生成十个需要大量修改的测试,未必比工程师写一个准确不变量更有价值。中继层只提供能力,是否继续使用取决于可重复的工程数据。

七、最小失败样本才是交付物:缩减、固化与沙箱回放

属性测试找到一次失败只是开始。随机生成的原始样本可能包含数百个对象、几十步事件和大量无关字段,直接交给开发者几乎无法分析。Shrink 的目标是在保持失败的前提下逐步删除事件、缩小数值、简化字符串和降低结构深度,得到最小反例。真正有工程价值的不是“运行十万次发现一次错误”,而是“稳定重现错误所需的最小条件”。

缩减过程必须保持输入合法。如果引擎把订单事件删到违反前置条件,得到的失败只是在验证输入校验。状态机生成器应知道哪些删除、交换或替换仍满足业务约束;跨字段对象需要整体缩减,而不是分别把金额和币种缩到不可能的组合。模型可以解释最小样本,却不应参与每一步 Shrink 决策,确定性算法更容易回放。

失败产物建议保存为 Counterexample Bundle。Bundle 包含 contract_id、property_id、source_commit、strategy_version、random_seed、minimal_input、effect_log、exception、environment_digest 和 first_seen。若失败来自差分执行,还要保存两侧版本及规范化差异;若来自变异测试,则保存 operator、mutation_location 和 mutant_digest。所有字段形成内容寻址的不可变对象。

最小反例进入回归集前需要去重。同一个舍入缺陷可能被十种随机路径触发,平台按失败栈、属性、Effect Log 和最小输入结构生成指纹。新样本若被已有回归样本覆盖,只增加 occurrence_count 和新的环境信息;若触发路径不同,则保留为独立样本。没有去重的反例仓很快会膨胀成执行缓慢、无人维护的历史档案。

回放环境要固定依赖、区域设置、时区、随机源和逻辑时钟。金额、日期、Unicode 和并发错误经常只在特定环境出现。容器镜像摘要、包锁文件、数据库 schema 版本和模拟服务版本都进入 environment_digest。对涉及并发的属性,保存调度轨迹或逻辑事件序列,仅仅保存一个随机 seed 通常不足以重建竞态。

执行沙箱默认关闭网络,只挂载只读源码、临时工作目录和测试依赖缓存。CPU、内存、进程数、文件大小和总时长均有上限。需要数据库时使用一次性实例或事务回滚环境;需要消息队列时使用按测试隔离的命名空间。模型提出的新依赖不能在测试期间动态下载,必须先经过供应链检查并进入锁文件。

失败分类也要保留“不确定”。同一用例连续回放三次结果不同,标记 nondeterministic;环境不完整,标记 environment_gap;契约互相矛盾,标记 contract_conflict;只有确认是实现违反已批准契约时,才标记 product_defect。把所有红灯都自动转成缺陷,会让工程师逐渐不信任系统。

修复后不能只验证最小样本。CI 先执行该 Bundle,确认问题消失;随后在最小样本附近做局部扰动,再运行完整属性预算和相关变异体。只修补一个固定值通常能通过回归样本,却会在相邻金额或事件序列上再次失败。反例工厂的职责是证明缺陷类别被消除,而不是证明一个案例被特殊处理。

反例也有生命周期。契约废弃、业务版本下线或依赖行为改变时,Bundle 不应直接删除,而是标记 retired,并记录替代契约与原因。仍服务于当前版本的反例必须定期回放;长期无法重现的样本进入调查,确认是环境漂移、测试错误还是行为变化。一个永远绿色但不再对应任何风险的回归集,同样是一种维护债务。

八、把测试证据接入 CI:门禁看风险增量,不看测试数量

反例工厂接入 CI 后,需要把多个信号组织成一份可解释的 Test Evidence。它至少包含契约通过情况、新增最小反例、变异体杀死率、幸存体解释、差分结果、flaky 比例、执行预算和环境摘要。发布系统读取结构化证据,而不是从控制台日志里搜索“passed”。

门禁应以风险增量为中心。普通 PR 不必为仓库中所有历史问题负责,但不能降低现有能力:已批准契约不得从通过变为失败,高风险目录不得增加未解释幸存体,已有 Counterexample Bundle 必须继续通过,测试预算不得无理由增长。若 PR 修改契约本身,需要领域负责人批准,并同时说明旧行为如何迁移。

不同信号不能简单加成一个总分。覆盖率从 85% 降到 84.8%,可能只是删除不可达代码;变异杀死率保持不变,却可能新增了一个高风险幸存体;属性测试全部通过,也可能因为生成器过滤掉了多数输入。门禁保留原始维度和 reason_code,让审核者知道究竟是行为失败、证据缺失、预算超限还是环境异常。

建议跟踪以下工程指标:每千次属性执行发现的独立反例数、最小反例平均缩减比、每分钟杀死的高风险变异体、幸存体平均关闭时间、错误变更逃逸率、flaky 测试占比、模型提案采纳率、提案人工修改量和单个有效属性的维护时长。这些指标同时约束发现能力、速度和维护成本。

模型提案的 A/B 评测必须在固定任务集上进行。任务集包含历史缺陷、手工注入错误、边界契约、等价变异和不完整上下文。评测时固定源码快照、上下文 manifest、测试预算和审核规则,比较不同模型能否提出可执行属性、能否杀死目标变异体、是否生成越权依赖及审核耗时。只比较生成代码的可读性,无法说明生产价值。

CI 性能使用分层预算。提交前运行确定性的回归 Bundle 和少量关键属性;PR 阶段运行受影响模块的扩展生成与增量变异;夜间运行全量变异和长序列状态机;发布候选运行跨版本差分与关键环境矩阵。每层都有最大时长和资源额度,超出预算先输出证据不足,而不是悄悄减少样本后仍标记通过。

缓存需要绑定测试语义。只有源码哈希、契约版本、生成器版本、依赖摘要和环境摘要全部相同,才能复用结果。对随机探索,可以复用已知反例,但不能把上次“未发现失败”当作本次通过。没有发现反例只是给定预算下的观察,不能被缓存成永久真理。

Flaky 治理应独立于产品缺陷。首次不稳定先隔离到诊断队列,保存每次执行轨迹;若影响高风险契约,门禁保持阻塞,不能靠重跑解锁;低风险探索可以暂时降级,但必须设置负责人和到期时间。重试成功率不是质量指标,重试次数增加通常说明环境、时钟或并发控制正在恶化。

Test Evidence 最终要能回答审核者的四个问题:这次改动触碰了哪些行为契约;系统用什么输入和关系验证它们;哪些故意注入的错误会被发现;仍有哪些无法判断的区域。答案完整时,代码智能体生成了多少文件并不重要。答案缺失时,再高的覆盖率也不应替代工程判断。

九、什么时候不该建立反例工厂:边界、组织责任与演进路线

属性测试并非所有模块的优先选择。简单的数据搬运、生命周期很短的原型、主要依赖人工视觉判断的页面,以及没有稳定行为契约的探索功能,投入复杂生成器和变异平台可能得不偿失。此时少量示例测试、端到端冒烟和人工验收更直接。工具选择应由故障代价、输入空间和维护周期决定。

如果团队连确定性单元测试、依赖锁定和稳定 CI 都没有,先补基础设施。属性测试会放大时钟、网络、共享数据库和全局状态带来的不确定性;变异测试会放大慢测试成本;模型会放大不清晰契约。基础不稳时同时引入三者,得到的通常是更多随机红灯,而不是更高质量。

另一个停止条件是 Oracle 无法建立。某些生成式体验只能由用户研究判断,强行把主观偏好写成属性会固化错误目标。可以验证格式、安全、延迟和资源上限,却不必宣称已经自动验证“内容更好”。反例工厂只处理能被明确表达、稳定观察和重复执行的部分。

落地可以从一条高价值不变量开始。选择历史故障集中、输入组合丰富且有领域负责人的模块;将一个事故转成契约;编写受约束生成器;确认最小反例能够复现;注入五到十个真实变异;接入增量 CI。只有当这条链稳定后,再扩大到状态机、差分执行和模型提案。一次铺满全仓库会制造大量无人认领的测试债务。

组织责任不能交给“AI 测试平台”一个团队。业务负责人批准契约,开发者维护可观察接口与生成器,测试工程师设计风险分布和变异策略,平台团队维护沙箱与证据格式,安全团队控制上下文和依赖,发布负责人决定门禁例外。模型没有责任主体资格,也不能成为失败豁免的批准人。

例外必须有期限。某个高风险幸存体暂时无法处理,需要记录原因、补偿措施、负责人和 expires_at;某条属性因第三方故障被禁用,需要保留最后一次证据和恢复条件;某个模型提案被拒绝,要有 reason_code。没有到期机制的例外会慢慢变成永久盲区。

模型升级时不要直接比较“新模型写了更多测试”。使用同一任务集回放,比较有效属性率、高风险变异杀死率、越权提案率、虚构接口率、人工修改量和总审核时间。若新模型擅长长解释却没有提高判别力,就没有必要迁移。开源项目、本地模型、云端服务或统一中继都只是候选能力来源,测试契约与证据格式应保持独立。

成熟度可以分为四级。第一级保存历史反例并稳定回放;第二级把关键业务规则写成属性并持续探索;第三级用差分和变异校准判别力;第四级才引入代码智能体分析盲区、提出契约和生成器变更。顺序不能倒过来,因为模型需要确定性证据来判断建议是否有价值。

反例工厂最终改变的不是测试数量,而是团队讨论质量的方式。过去审核者问“覆盖率够不够”,现在会问“哪个不变量保护这次改动”“什么错误能让它失败”“最小反例是什么”“还有哪些行为无法判定”。这些问题把绿色 CI 从一种心理安慰,变成可以质疑和复查的工程证据。

代码智能体最适合做的,也不是替开发者预测所有正确输出。它更适合从历史缺陷、幸存变异和契约空白中提出新的攻击角度,再让属性测试、差分执行和沙箱回放去验证。模型负责扩大假设空间,确定性工具负责给出事实,人类负责定义行为与承担发布决策。

当一条测试能够杀死真实错误、生成最小反例、关联业务契约并在受控环境稳定回放时,它才真正增加了系统可信度。相比“又生成了三百个单元测试”,这条标准更苛刻,也更接近代码智能体进入 IT 研发体系后应该创造的长期价值。

返回列表