想象你是一家餐厅的主厨。每天开店之前,你都要确认所有菜品能端上桌——番茄是新鲜的、酱汁咸淡合适、整道菜摆盘没有问题。如果每次都要等顾客点餐后才开始检查,餐厅早就倒闭了。于是你设计了一套“开店前自动检查流水线”:一颗颗尝番茄、一道道试半成品、最后请试吃员完整模拟一餐。这就是持续集成(CI)测试自动化的原型。本文要讲的,就是用这套“厨房流水线”的思维,为 AI 工程项目搭建 CI 测试自动化体系:测试金字塔决定检查重点,测试策略制定检查标准,并行测试让多个炉灶同时开工,测试缓存让没动过的食材不再重尝,失败分析找出“是菜谱错了还是厨师手抖”,质量门禁决定什么菜能出锅,工程实践回答如何在 AI 项目里落地这一整套流程。读完你会建立一套清晰的工程直觉。
CI 测试自动化是模型从实验走向产品的“质量守门员”。掌握金字塔分层、并行执行、缓存与失败分析,能让你的模型发布又快又稳,是 AI 测试工程师的核心竞争力。
可视化演示
演示FCFS进程调度算法:按到达顺序执行进程,展示等待时间与周转时间的计算过程。
本节配有交互动效(进入视口后自动加载,约 3KB)。
FCFS调度 按到达顺序执行,非抢占式 WT=开始-到达 TAT=完成-到达
测试金字塔
开店前,主厨有一套“三层尝菜法”。第一层,逐颗尝番茄、逐根掐葱——量大、几秒钟一颗;第二层,把腌好的肉和调好的酱组合成半成品,逐碗试味;第三层,请试吃员从点单、上菜到用餐完整走一遍。三层各有各的节奏:越往下越频繁、越多、越快,越往上越接近真实、越慢、越少。
这套三层结构在持续集成里叫测试金字塔。最底层是单元测试,只验证一个函数或类的行为,毫秒级完成;中间是集成测试,验证模块之间协作是否吻合;最顶层是端到端测试(E2E),拉起完整应用模拟真实用户走完核心业务链路。金字塔的形态决定了资源分配:底座大、塔尖小。
经验比例大约是 7:2:1。为什么端到端测试要少?因为它每跑一次都要启动全部服务、依赖数据库和网络环境,慢则分钟级且极易受环境抖动影响;单元测试则快而稳,出问题还能精确到函数级别定位。CI 的策略不是“哪个层更真实就多写哪个”,而是让每一层干自己最擅长的活。
试吃员再专业,也不可能把每道菜的每个版本都吃一遍,否则成本和等待时间都会失控。聪明的厨房是底料快检做足、整菜抽查把关、全流程精检留给招牌菜——测试金字塔教给我们的正是这种分层智慧:让最快的测试铺满地基,让最贵的测试守住命门。
- 单元测试数\(N_{unit}\) 表示 CI 中单元测试用例的总条数,通常数量最多,负责逐个验证最小功能块。
- 集成测试数\(N_{integration}\) 表示模块间协作验证的用例数,数量居中,负责把拼好的部件连起来试。
- 端到端测试数\(N_{e2e}\) 表示全链路模拟的用例数,数量最少但最关键,只覆盖核心用户旅程。
- 近似比例三者之比约为 7:2:1,具体项目可按风险与成本微调,但“下大上小”的形态不能反。
测试策略
不同档期,备餐方案要变化。平日晚市以底料快检为主,只抽查几道热门菜;节假日午市客人多,得提前做一轮完整试菜演练。策略就是定下三件事:尝什么、尝多细、在什么环境里尝。
落到测试工程上,这三件事对应分层原则(把资源按金字塔比例分配)、覆盖目标(规定核心代码要达到多高的覆盖率)和环境配置(为 CI 提供隔离的沙盒环境、固定种子数据的测试库、可 mock 的外部服务)。策略不是拍脑袋,而是让每一条测试都有明确的目的和归属。
覆盖率是最常用的体检指标,公式如下。要注意覆盖率不是越高越好:数据清洗脚本、装饰器、配置代码追 100% 纯属浪费;而模型推理、支付结算这类核心逻辑,覆盖率低于 80% 就值得警惕。目标要分模块定,不能一刀切。
策略定得好,后厨才不会乱忙。就像主厨不会让备菜师傅去管试吃员的事,测试策略不让集成测试抢 E2E 的活,也不让单元测试背覆盖全部逻辑的锅——各司其职,整个 CI 才能又快又不漏。
- 已覆盖行数\(L_{covered}\) 是测试执行过程中真正跑到的代码行数,衡量测试触碰的范围。
- 总行数\(L_{total}\) 是被测模块的全部代码行数,作为覆盖的基数。
- 覆盖比例两者相除得到 0 到 1 之间的小数,表示测试触及了多大比例的代码。
- 转为百分比乘以 100 后得到直观的百分数,便于与质量门禁的阈值比较。
并行测试
一位厨师一道菜一道菜地尝,一小时才能完成全部备餐检查;让四位厨师各守一个炉灶同时开工,十五分钟就能搞定。但无论多少位厨师开工,总得有人分任务、汇总意见——这部分是没法并行的。
并行测试正是这样把测试任务切成多片(shard),分配到多个执行器上同时跑。切片时要注意资源隔离:不同分片不能共享同一个数据库、同一个临时目录,否则就像几位厨师抢同一口锅,互相污染数据,测试结果时红时绿。
并行能加速多少?Amdahl 定律给出了答案:即使执行器无限多,串行部分(调度、汇总、启动)也会拖住后腿。假设串行占 20%,那加速上限就是 5 倍,加再多机器也突破不了。
实践建议:先统计流水线各阶段耗时,把资源调度和报告生成等串行段压到最短,再谈加执行器。几位厨师同时炒菜,锅要够、台面要分、菜谱要统一——并行测试的提速,永远始于把“争抢”变成“隔离”。
- 加速比\(S\) 表示并行后耗时与串行耗时的比值,多少倍提速一目了然。
- 可并行比例\(P\) 是任务中能被并行执行的比例,例如 80% 的测试可以分片同时跑。
- 执行器数量\(N\) 是同时工作的执行机/容器的个数,等于同时开工的“炉灶数”。
- 分母组装串行部分 \((1-P)\) 加上并行部分除以 \(N\),两者共同决定总耗时。
测试缓存
厨房里每个调料罐都贴着“已验讫:某月某日某主厨”的标签。只要这罐酱油从上次检查后没被开封、没换过货,下一个班次就不用再尝一遍。省下的时间,用来盯真正新到的食材。
测试缓存就是这套“已验讫”机制:构建工具根据文件哈希与依赖图,计算出“哪些测试受本次代码变更影响”,只重跑受影响的部分,其余直接标记通过。核心是变更感知——精准判断一个测试的依赖到底变没变。
缓存最怕隐性依赖:测试读了全局环境变量、依赖系统时钟、悄悄访问了外部服务。这些“藏在暗处的依赖”一旦变化,分析器却以为没变,就会造成漏测上线。所以缓存要保守:宁可多跑,不可漏跑。
缓存的价值是“让没变的食材不再重尝”,而不是“想方设法少尝”。技术负责人要盯的是变更分析的正确性——标签贴错了,省下的时间迟早会以线上事故的方式还回去。
- 命中缓存数\(N_{hit}\) 是依赖未变化、被安全跳过的测试条数,它们从缓存直接复用结果。
- 未命中数\(N_{miss}\) 是依赖发生变化、必须真实重跑的测试条数。
- 总和分母命中与未命中相加得到本次提交涉及的测试总数。
- 命中率比值两者相除得到命中率,越高说明 CI 越“省力”,但它只是结果,不是优化目标。
失败分析
一道菜被顾客退回来,厨房要马上诊断:是菜谱写错了(代码缺陷)?是番茄供应商换了导致口味变了(环境变化)?还是今天厨师手抖盐放多了(偶发抖动)?诊断错方向,重做一百遍也白搭。
CI 的失败分析同样是先分类再动手。常见三类:确定性失败——代码有 bug,重试多少次都红,必须修代码;环境性失败——依赖服务没就绪、容器被杀了,修复或重试环境即可;脆断性失败(flaky)——同一份代码多次运行结果不稳定,像极了厨师偶尔手抖。
自动重试机制对后两类有帮助,但 flaky 不能靠重试掩盖,要治理:标记、追踪、隔离,反复发作者降级甚至删除。衡量 flaky 严重程度用脆断率。
退菜不可怕,可怕的是不查病因就原样重做。优秀的厨房有一本“退菜登记簿”——失败的测试都要留下分类记录,确定性失败进缺陷库,环境性失败查基础设施,flaky 单独挂账,直到治理清零。
- 脆断测试数\(N_{flaky}\) 是统计周期内被判定为结果不稳定的测试条数。
- 测试总数\(N_{total}\) 是该周期内执行的全部测试条数,作为分母基线。
- 占比计算两者相除得到 flaky 在全部测试中的占比,越低越好。
- 百分比与阈值乘以 100% 后与 3% 的经验线比较,超线就要专项治理。
质量门禁
主厨在出菜口把关:每道菜出锅前必须经过他这一关,色泽、温度、摆盘合格才盖章,不合格当场打回。这个“出菜口盖章”,就是 CI 里的质量门禁:只有测试通过率达到门槛、关键用例全绿,代码才允许合并或发布。
设计质量门禁要定三件事:门槛指标(通过率、关键用例、覆盖率下限)、阻塞规则(不达标就拒绝合并,需要负责人豁免才能放行)、趋势监控(观察近 30 天门禁拒绝率,指标突然恶化说明策略失效)。门禁是工程权衡,不是越严越好。
一条常见的门禁是:总体通过率不低于 99%,且标记为 critical 的用例必须 100% 通过。
主厨把关太松,坏菜端上桌砸招牌;把关太严,后厨怨声载道、偷偷走捷径。所以门禁要分层:关键菜全检,普通菜抽检,再配合豁免记录和趋势看板——让规则被信任,而不是被绕过。
- 通过用例数\(N_{pass}\) 是本次 CI 运行中真实执行且通过的测试条数。
- 运行用例数\(N_{run}\) 是本次运行实际执行的测试总条数(缓存跳过的另计)。
- 通过率两者相除得到本次运行的通过率,是门禁判断的直接依据。
- 门禁阈值\( \geq 99\% \) 表示通过率不能低于 99%,守住这条线才放行。
工程实践
餐厅开了半年,要算总账:备菜速度为什么忽快忽慢?瓶颈在洗菜还是炒菜?煮烂的食材成本有多少?工程实践就是 CI 体系的经营复盘——把测试金字塔、并行、缓存、门禁真正管起来。
在 AI 项目里,这套体系要往外延伸,因为被测对象不只是代码,还有模型和数据:模型重新训练后要跑评估指标回归(防止效果回退)、数据管线变更后要做 schema 与分布校验、推理服务升级前要跑性能基线对比。这些都是 AI 项目“新加的菜”。
衡量整个 CI 自动化体系好不好,最核心的 KPI 是反馈时长——从代码提交到拿到测试结果的总时间。
经营复盘最后还要看维护成本:定期清理无效用例、合并重复覆盖、把测试数据版本化。就像主厨定期清点调料、扔掉过期食材——好的 CI 体系不是越建越大,而是越养越精,让每次提交都又快又稳地给出可信结论。
- 排队时间\(T_{queue}\) 是代码提交后等待执行资源的时间,受执行器数量与任务负载影响。
- 执行时间\(T_{exec}\) 是测试真正运行的时间,受金字塔结构、并行度与缓存命中率共同影响。
- 报告时间\(T_{report}\) 是结果汇聚、生成报告与产物归档的时间,串行段要刻意压短。
- 求和与目标三段相加就是开发者感知的“提交到结果”全流程,主流水线目标 10 分钟内。
AI 应用场景
场景一:LLM 应用的 Prompt 回归测试门禁
阶段:持续集成(模型与 Prompt 变更验证)
解决的问题:传统做法靠人工抽查 Prompt 效果,改一个 Prompt 可能让其他场景的回复质量悄悄崩坏却无人察觉。AI 方案是在 CI 中维护一组“黄金问答集”,每次 Prompt 或模型版本变更,流水线自动运行这批问答并比对输出质量分数,低于阈值立刻阻断合并。这就像主厨固定让试吃员试几道招牌菜,保证口味不回退。
具体案例:某客服机器人的 Prompt 从 v12 升到 v13,CI 的 200 条黄金问答中有 12 条回答质量明显下降,流水线自动拦截。测试工程师凭失败报告定位到是“格式要求过严”导致回复被截断,回滚后修复再上线。
场景二:数据管线变更的缓存与失败分析
阶段:数据验证(特征与训练数据变更)
解决的问题:离线训练数据每天都有新版本,全量校验耗时数小时,且失败原因混杂难查。AI 方案是给数据字段生成“指纹”(schema 哈希加分布统计),基于变更感知只校验真正变化的字段;失败分类自动区分“数据漂移”(需人工评估)与“调度抖动”(自动重试即可),让工程师把时间花在刀刃上。
具体案例:某推荐系统的训练数据新增一个特征列,CI 依靠数据指纹缓存只重跑了该特征相关的校验任务,全量校验从 1.5 小时压缩到 12 分钟;期间一次偶发的数据平台抖动被自动重试消化,没有打扰任何人。
场景三:推理服务性能回归门禁
阶段:发布前验证(模型服务升级)
解决的问题:模型推理服务升级后延迟偶发爆炸,过去上线后靠线上监控才发现,已经影响真实用户。AI 方案是在 CI 中分片并行跑性能压测,结合缓存跳过无关用例,把本次服务的 P99 延迟与基线对比,超过阈值(如 10%)即阻塞发布。
具体案例:某图像识别服务把模型从 ResNet 换成 EfficientNet,CI 压测发现 P99 延迟从 80ms 升到 115ms,门禁拦截了这次看似“应该更快”的升级。团队排查后发现是量化配置错误,修正后 P99 回落到 72ms 才放行。
Python 演示
"""迷你 CI 测试自动化模拟器: 金字塔分层, 并行执行, 缓存命中, 失败分析."""import randomimport timefrom concurrent.futures import ThreadPoolExecutor class TestCase: """一条测试用例: 所属层级, 耗时, 是否脆断(flaky).""" def __init__(self, name, layer, duration, flaky=False): self.name = name self.layer = layer self.duration = duration self.flaky = flaky def run(self): # 脆断用例有 25% 概率偶发失败 if self.flaky and random.random() < 0.25: return False time.sleep(self.duration) return True class CiRunner: """模拟 CI 流水线: 变更感知 -> 并行执行 -> 结果统计.""" def __init__(self, tests): self.tests = tests self.hit_count = 0 self.exec_count = 0 self.deps = {} services = ["svc_a", "svc_b", "data_x"] for i, t in enumerate(tests): self.deps[t.name] = [services[i % len(services)]] def affected(self, changed_files): # 变更感知: 只选择依赖文件与本次变更相交的测试 names = [t.name for t in self.tests if set(self.deps[t.name]) & set(changed_files)] return names or [t.name for t in self.tests] def pipeline(self, changed_files, workers=4): todo = self.affected(changed_files) self.hit_count += len(self.tests) - len(todo) runnable = [t for t in self.tests if t.name in todo] with ThreadPoolExecutor(max_workers=workers) as pool: results = list(pool.map(lambda t: t.run(), runnable)) self.exec_count += len(results) passed = sum(results) total = len(todo) return {"total": total, "passed": passed, "rate": passed / total if total else 1.0} def make_suite(): # 测试金字塔: 单元 10 条, 集成 4 条, E2E 2 条(其中 1 条脆断) suite = [TestCase("unit_%d" % i, "unit", 0.02) for i in range(10)] suite += [TestCase("integ_%d" % i, "integration", 0.08) for i in range(4)] suite.append(TestCase("e2e_order", "e2e", 0.25, flaky=True)) suite.append(TestCase("e2e_pay", "e2e", 0.25)) return suite def main(): runner = CiRunner(make_suite()) for round_no in range(1, 4): report = runner.pipeline(["svc_a"]) print("第 %d 轮: 运行 %d 条, 通过 %d 条, 通过率 %.0f%%, " "缓存命中 %d, 累计执行 %d" % (round_no, report["total"], report["passed"], report["rate"] * 100, runner.hit_count, runner.exec_count)) if report["rate"] < 1.0: print(" 失败分析: e2e_order 疑似脆断, 自动重试 1 次") retry = TestCase("e2e_order", "e2e", 0.25).run() print(" 重试结果: %s" % ("通过" if retry else "仍失败")) if __name__ == "__main__": main()
svc_a,因此只有依赖它的测试会被执行(缓存跳过其余),测试用线程池并行跑,通过率不足时触发失败分析与 flaky 重试。suite = [TestCase("unit_%d" % i, "unit", 0.02) for i in range(10)]生成 10 条毫秒级单元测试,对应金字塔底层suite.append(TestCase("e2e_order", "e2e", 0.25, flaky=True))E2E 用例少而慢,还故意埋了一条脆断用例self.deps[t.name] = [services[i % len(services)]]模拟每条测试依赖某个服务文件,作为缓存判断依据names = [t.name for t in self.tests if set(self.deps[t.name]) & set(changed_files)]变更感知:只挑依赖文件被改动的测试self.hit_count += len(self.tests) - len(todo)没被选中的测试视为缓存命中并跳过pool.map(lambda t: t.run(), runnable)用线程池并行执行测试,模拟多执行器分片if report["rate"] < 1.0:通过率不足时触发失败分析分支retry = TestCase("e2e_order", "e2e", 0.25).run()对 flaky 用例重试一次,验证是否为偶发
常见误区
误区我把所有关键路径都写成 E2E 测试,一个用户流程一条,这样最贴近真实用户,出错肯定能拦住。单元测试太琐碎了,懒得写。
正解端到端测试慢(秒级到分钟级)、脆(依赖服务一抖动就红)、贵(每条都要维护整套环境)。金字塔下大上小才是正确资源分配:底层单元测试毫秒级、覆盖面广、定位精确;E2E 只留给核心链路。全 E2E 会让 CI 从 5 分钟膨胀到 2 小时,且失败时极难定位是哪一环出了问题。
为何常见:E2E 是“看起来最真”的测试,人的直觉是越接近真实越安全,却忽略了它的成本、速度与稳定性都是最差的。
误区CI 变慢了?加 10 台执行机,10 倍并行,测试时间就变成十分之一,成本无非是机器钱。
正解Amdahl 定律决定了加速上限:任务总有串行部分(资源调度、结果合并、报告生成),即使执行器无限多,加速比也趋近于 1/(1-P)。此外共享数据库、缓存服务在并行数过高时会发生资源争抢,测试反而更慢。正确做法是先测量串行占比,再合理设置分片数。
为何常见:把并行当成“人多力量大”的直觉模型,忽略了调度开销和共享资源的物理约束。
误区缓存命中率都到 95% 了,CI 快到飞起。我要把更多测试加进跳过名单,让指标更好看。
正解缓存的前提是“变更影响分析准确”。跳过本质上是信任“这些测试的依赖没变”,但全局状态、系统时间、外部服务版本等隐性依赖会让分析失效。一旦漏掉真正受影响的测试,缺陷会直接进生产。缓存要保守——宁可多跑不可漏跑;命中率是结果指标,不是优化目标。
为何常见:命中率直观、易优化,让人觉得“省到就是赚到”,而正确性这种看不见的风险被选择性忽略了。
误区CI 红灯了,先 Retry 三次。反正是偶发问题,重试通过了就当没发生过,别耽误发布。
正解失败必须先分类。确定性失败(代码缺陷)重试一百次也红,必须修代码;环境性失败(依赖服务未就绪)重试有意义;flaky 脆断要标记、追踪、隔离,而不是默认靠重试掩盖。把一切失败交给重试,就像电梯门夹人时只按“关门”而不检修——安全隐患全被掩盖了。
为何常见:重试按钮成本最低、见效最快,团队在发布压力下倾向“先绿再说”,最终导致红灯疲劳和缺陷积累。
误区门禁设成所有测试必须 100% 通过,一个都不许红,这样质量绝对稳。
正解门禁过严会大幅阻塞开发节奏,团队会产生对抗行为:强制合并、删难测用例、降低测试标准。正确做法是分层门禁——关键用例(critical)必须全绿,非关键用例允许阈值(如通过率 ≥ 99%),并配合豁免流程与趋势监控,允许“有记录的特例”存在。
为何常见:质量焦虑让人倾向“一刀切”最严格,却忘了门禁是工程权衡而非绝对命令,过严的防线终将被绕过。
面试题
Q1. 测试金字塔中各层用例数量的经验比例最接近?
- A.单元七成集成二成 E2E 一成← 正确答案
- B.单元一成集成二成 E2E 七成
- C.单元二成集成七成 E2E 一成
- D.三层用例数量大致均等分布
答案:A。金字塔原则要求底大上小:单元测试毫秒级、成本低,数量最多;E2E 秒级且依赖整套环境,只保留核心链路。就像厨房里逐颗尝番茄(单元)最多,完整试餐(E2E)最少。B 和 C 颠倒了层级分配,D 忽视了成本差异。
Q2. 单元测试最突出的运行特征是什么?
- A.毫秒级完成且用例数量庞大← 正确答案
- B.分钟级完成且贴近真实用户
- C.依赖完整数据库与外部服务
- D.只能由专职测试人员编写
答案:A。单元测试只验证单个函数或类,毫秒级、数量多、成本低,是金字塔的底座,如同主厨逐颗尝番茄。B 是 E2E 的特征,C 是集成或 E2E 的依赖,D 说法错误——开发者日常也在写单元测试。
Q3. 若可并行比例 P=0.8,执行器数 N=4,Amdahl 加速比约为?
- A.2.5 倍提速← 正确答案
- B.4.0 倍提速
- C.0.4 倍提速
- D.1.6 倍提速
答案:A。代入公式 S=1/((1-0.8)+0.8/4)=1/(0.2+0.2)=2.5。并非 4 倍,因为 20% 的串行部分(调度与汇总)拖了后腿。如同四位厨师同时尝菜,但分菜和汇总意见仍要一个人完成。
Q4. 测试缓存能够合法跳过的前提是什么?
- A.准确判断测试依赖未被变更← 正确答案
- B.只要流水线变快就大胆跳过
- C.每周定时清理并全部重跑
- D.随机挑选一半测试来执行
答案:A。缓存本质是“依赖未变则结果可复用”,前提是变更感知分析准确。调料罐贴了“已验讫”标签,但若有人偷偷换了酱,标签就失效了。B 把速度当目的,C 是定期全量,D 是随机抽样,都会导致漏测。
Q5. 面对一条 flaky 脆断测试,标准动作是什么?
- A.先分类标记再隔离与治理← 正确答案
- B.每次失败都无脑自动重试
- C.立即把该测试直接删除
- D.提高门禁通过率来掩盖
答案:A。flaky 指同一代码多次运行结果不稳定,要先确认它与代码变更无关,再标记、隔离、必要时降级删除。就像厨师手抖导致退菜,要记录是谁在抖,而不是假装没事。B 掩盖问题,C 丢失覆盖率,D 混淆指标。
Q6. 质量门禁设置过严的典型恶果是什么?
- A.开发对抗门禁并绕过流程← 正确答案
- B.测试覆盖率自动得到提升
- C.流水线执行成本明显下降
- D.缓存命中率随之大幅提高
答案:A。门禁是工程权衡,不是越严越好。门槛过高会阻塞合并,团队会以强制合并、删难测用例等方式绕过防线,最终门禁信用崩塌。就像主厨把每盘菜都退回重做,后厨迟早会偷工减料或另寻出路。
Q7. AI 项目中模型评估回归测试的作用是什么?
- A.防止模型变更导致效果回退← 正确答案
- B.替代全部代码单元测试
- C.只验证代码能否编译通过
- D.只能靠人工抽样来评估
答案:A。AI 项目的被测对象不只是代码,还有模型本身。在 CI 中挂黄金评估集,每次模型版本变更自动比对指标,防止“改一处、崩一片”。相当于主厨请试吃员固定试招牌菜保证口味不回退。B 不可替代,C 遗漏模型,D 否认了自动化的可能。
Q8. 关于并行测试的正确做法有哪些?
- A.保证各分片之间完全独立← 正确答案
- B.对共享资源做好隔离← 正确答案
- C.执行器数量无限增加
- D.忽略串行部分的存在
答案:AB。并行测试的核心是独立与隔离:每片有自己的数据与环境,不共享可变状态,如同多位厨师各用各的炉灶。C 错——执行器过多会资源争抢;D 错——串行部分无法消除,只能尽量压缩。
Q9. 失败分析中常见的失败类型有哪些?
- A.代码缺陷导致的确定性失败← 正确答案
- B.环境异常导致的环境性失败← 正确答案
- C.结果不稳定的脆断性失败← 正确答案
- D.缓存命中导致的跳过型失败
答案:ABC。退菜要分三种病因:菜谱错了(确定性,必须修代码)、供应商换了(环境性,修环境或重试)、厨师手抖(flaky,跟踪隔离)。D 是设计内的正常行为,不属于失败类型。
Q10. 测试策略中覆盖率目标应如何制定?
- A.核心逻辑追求较高的覆盖率← 正确答案
- B.非核心代码允许适当放松← 正确答案
- C.所有代码划一要求百分百
- D.只看行覆盖忽略分支覆盖
答案:AB。覆盖率是校验手段不是目的。核心逻辑(如模型推理、支付)要求 90% 以上,配置与脚本可放宽。C 一刀切会推高成本;D 忽略分支覆盖可能漏掉某个 if 分支的 bug——如同主厨只数尝了几颗番茄,却忘了试两种口味。