想象你是一家餐厅的主厨。每天开店之前,你都要确认所有菜品能端上桌——番茄是新鲜的、酱汁咸淡合适、整道菜摆盘没有问题。如果每次都要等顾客点餐后才开始检查,餐厅早就倒闭了。于是你设计了一套“开店前自动检查流水线”:一颗颗尝番茄、一道道试半成品、最后请试吃员完整模拟一餐。这就是持续集成(CI)测试自动化的原型。本文要讲的,就是用这套“厨房流水线”的思维,为 AI 工程项目搭建 CI 测试自动化体系:测试金字塔决定检查重点,测试策略制定检查标准,并行测试让多个炉灶同时开工,测试缓存让没动过的食材不再重尝,失败分析找出“是菜谱错了还是厨师手抖”,质量门禁决定什么菜能出锅,工程实践回答如何在 AI 项目里落地这一整套流程。读完你会建立一套清晰的工程直觉。

岗位关联

CI 测试自动化是模型从实验走向产品的“质量守门员”。掌握金字塔分层、并行执行、缓存与失败分析,能让你的模型发布又快又稳,是 AI 测试工程师的核心竞争力。

目录
  1. 可视化演示
  2. 测试金字塔
  3. 测试策略
  4. 并行测试
  5. 测试缓存
  6. 失败分析
  7. 质量门禁
  8. 工程实践
  9. AI 应用场景
  10. Python 演示
  11. 常见误区
  12. 面试题

可视化演示

演示FCFS进程调度算法:按到达顺序执行进程,展示等待时间与周转时间的计算过程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

FCFS调度 按到达顺序执行,非抢占式 WT=开始-到达 TAT=完成-到达

测试金字塔

开店前,主厨有一套“三层尝菜法”。第一层,逐颗尝番茄、逐根掐葱——量大、几秒钟一颗;第二层,把腌好的肉和调好的酱组合成半成品,逐碗试味;第三层,请试吃员从点单、上菜到用餐完整走一遍。三层各有各的节奏:越往下越频繁、越多、越快,越往上越接近真实、越慢、越少。

这套三层结构在持续集成里叫测试金字塔。最底层是单元测试,只验证一个函数或类的行为,毫秒级完成;中间是集成测试,验证模块之间协作是否吻合;最顶层是端到端测试(E2E),拉起完整应用模拟真实用户走完核心业务链路。金字塔的形态决定了资源分配:底座大、塔尖小。

经验比例大约是 7:2:1。为什么端到端测试要少?因为它每跑一次都要启动全部服务、依赖数据库和网络环境,慢则分钟级且极易受环境抖动影响;单元测试则快而稳,出问题还能精确到函数级别定位。CI 的策略不是“哪个层更真实就多写哪个”,而是让每一层干自己最擅长的活。

试吃员再专业,也不可能把每道菜的每个版本都吃一遍,否则成本和等待时间都会失控。聪明的厨房是底料快检做足、整菜抽查把关、全流程精检留给招牌菜——测试金字塔教给我们的正是这种分层智慧:让最快的测试铺满地基,让最贵的测试守住命门。

Nunit:Nintegration:Ne2e≈7:2:1\displaystyle N_{unit} : N_{integration} : N_{e2e} \approx 7 : 2 : 1
这条公式表示三层用例数量的经验比例:单元测试最多、集成测试中等、端到端测试最少,是行业里的常见起步配置。
  1. 单元测试数\(N_{unit}\) 表示 CI 中单元测试用例的总条数,通常数量最多,负责逐个验证最小功能块。
    NunitN_{unit}
  2. 集成测试数\(N_{integration}\) 表示模块间协作验证的用例数,数量居中,负责把拼好的部件连起来试。
    NintegrationN_{integration}
  3. 端到端测试数\(N_{e2e}\) 表示全链路模拟的用例数,数量最少但最关键,只覆盖核心用户旅程。
    Ne2eN_{e2e}
  4. 近似比例三者之比约为 7:2:1,具体项目可按风险与成本微调,但“下大上小”的形态不能反。
    ≈7:2:1\approx 7 : 2 : 1

测试策略

不同档期,备餐方案要变化。平日晚市以底料快检为主,只抽查几道热门菜;节假日午市客人多,得提前做一轮完整试菜演练。策略就是定下三件事:尝什么、尝多细、在什么环境里尝。

落到测试工程上,这三件事对应分层原则(把资源按金字塔比例分配)、覆盖目标(规定核心代码要达到多高的覆盖率)和环境配置(为 CI 提供隔离的沙盒环境、固定种子数据的测试库、可 mock 的外部服务)。策略不是拍脑袋,而是让每一条测试都有明确的目的和归属。

覆盖率是最常用的体检指标,公式如下。要注意覆盖率不是越高越好:数据清洗脚本、装饰器、配置代码追 100% 纯属浪费;而模型推理、支付结算这类核心逻辑,覆盖率低于 80% 就值得警惕。目标要分模块定,不能一刀切。

策略定得好,后厨才不会乱忙。就像主厨不会让备菜师傅去管试吃员的事,测试策略不让集成测试抢 E2E 的活,也不让单元测试背覆盖全部逻辑的锅——各司其职,整个 CI 才能又快又不漏。

C=LcoveredLtotal×100%\displaystyle C = \frac{L_{covered}}{L_{total}} \times 100\%
行覆盖率 = 被测试执行到的代码行数 ÷ 总代码行数 × 100%。它衡量“尝过多少食材”,不衡量“尝得对不对”。
  1. 已覆盖行数\(L_{covered}\) 是测试执行过程中真正跑到的代码行数,衡量测试触碰的范围。
    LcoveredL_{covered}
  2. 总行数\(L_{total}\) 是被测模块的全部代码行数,作为覆盖的基数。
    LtotalL_{total}
  3. 覆盖比例两者相除得到 0 到 1 之间的小数,表示测试触及了多大比例的代码。
    LcoveredLtotal\frac{L_{covered}}{L_{total}}
  4. 转为百分比乘以 100 后得到直观的百分数,便于与质量门禁的阈值比较。
    ×100%\times 100\%

并行测试

一位厨师一道菜一道菜地尝,一小时才能完成全部备餐检查;让四位厨师各守一个炉灶同时开工,十五分钟就能搞定。但无论多少位厨师开工,总得有人分任务、汇总意见——这部分是没法并行的。

并行测试正是这样把测试任务切成多片(shard),分配到多个执行器上同时跑。切片时要注意资源隔离:不同分片不能共享同一个数据库、同一个临时目录,否则就像几位厨师抢同一口锅,互相污染数据,测试结果时红时绿。

并行能加速多少?Amdahl 定律给出了答案:即使执行器无限多,串行部分(调度、汇总、启动)也会拖住后腿。假设串行占 20%,那加速上限就是 5 倍,加再多机器也突破不了。

实践建议:先统计流水线各阶段耗时,把资源调度和报告生成等串行段压到最短,再谈加执行器。几位厨师同时炒菜,锅要够、台面要分、菜谱要统一——并行测试的提速,永远始于把“争抢”变成“隔离”。

S=1(1−P)+PN\displaystyle S = \frac{1}{(1-P) + \frac{P}{N}}
加速比 S = 1 ÷(串行比例 + 可并行比例 ÷ 执行器数)。它告诉我们:并行不是堆机器,先压缩串行部分才是关键。
  1. 加速比\(S\) 表示并行后耗时与串行耗时的比值,多少倍提速一目了然。
    SS
  2. 可并行比例\(P\) 是任务中能被并行执行的比例,例如 80% 的测试可以分片同时跑。
    PP
  3. 执行器数量\(N\) 是同时工作的执行机/容器的个数,等于同时开工的“炉灶数”。
    NN
  4. 分母组装串行部分 \((1-P)\) 加上并行部分除以 \(N\),两者共同决定总耗时。
    (1−P)+PN(1-P) + \frac{P}{N}

测试缓存

厨房里每个调料罐都贴着“已验讫:某月某日某主厨”的标签。只要这罐酱油从上次检查后没被开封、没换过货,下一个班次就不用再尝一遍。省下的时间,用来盯真正新到的食材。

测试缓存就是这套“已验讫”机制:构建工具根据文件哈希与依赖图,计算出“哪些测试受本次代码变更影响”,只重跑受影响的部分,其余直接标记通过。核心是变更感知——精准判断一个测试的依赖到底变没变。

缓存最怕隐性依赖:测试读了全局环境变量、依赖系统时钟、悄悄访问了外部服务。这些“藏在暗处的依赖”一旦变化,分析器却以为没变,就会造成漏测上线。所以缓存要保守:宁可多跑,不可漏跑。

缓存的价值是“让没变的食材不再重尝”,而不是“想方设法少尝”。技术负责人要盯的是变更分析的正确性——标签贴错了,省下的时间迟早会以线上事故的方式还回去。

R=NhitNhit+Nmiss\displaystyle R = \frac{N_{hit}}{N_{hit} + N_{miss}}
缓存命中率 R = 命中缓存被跳过的测试数 ÷ 全部测试数。它描述“多少食材靠着验讫标签免于重尝”。
  1. 命中缓存数\(N_{hit}\) 是依赖未变化、被安全跳过的测试条数,它们从缓存直接复用结果。
    NhitN_{hit}
  2. 未命中数\(N_{miss}\) 是依赖发生变化、必须真实重跑的测试条数。
    NmissN_{miss}
  3. 总和分母命中与未命中相加得到本次提交涉及的测试总数。
    Nhit+NmissN_{hit} + N_{miss}
  4. 命中率比值两者相除得到命中率,越高说明 CI 越“省力”,但它只是结果,不是优化目标。
    NhitNhit+Nmiss\frac{N_{hit}}{N_{hit} + N_{miss}}

失败分析

一道菜被顾客退回来,厨房要马上诊断:是菜谱写错了(代码缺陷)?是番茄供应商换了导致口味变了(环境变化)?还是今天厨师手抖盐放多了(偶发抖动)?诊断错方向,重做一百遍也白搭。

CI 的失败分析同样是先分类再动手。常见三类:确定性失败——代码有 bug,重试多少次都红,必须修代码;环境性失败——依赖服务没就绪、容器被杀了,修复或重试环境即可;脆断性失败(flaky)——同一份代码多次运行结果不稳定,像极了厨师偶尔手抖。

自动重试机制对后两类有帮助,但 flaky 不能靠重试掩盖,要治理:标记、追踪、隔离,反复发作者降级甚至删除。衡量 flaky 严重程度用脆断率。

退菜不可怕,可怕的是不查病因就原样重做。优秀的厨房有一本“退菜登记簿”——失败的测试都要留下分类记录,确定性失败进缺陷库,环境性失败查基础设施,flaky 单独挂账,直到治理清零。

Fflaky=NflakyNtotal×100%\displaystyle F_{flaky} = \frac{N_{flaky}}{N_{total}} \times 100\%
脆断率 = 一段时间内 flaky 测试数 ÷ 测试总数 × 100%。经验阈值是 3%,超过就说明测试体系开始“谎报军情”。
  1. 脆断测试数\(N_{flaky}\) 是统计周期内被判定为结果不稳定的测试条数。
    NflakyN_{flaky}
  2. 测试总数\(N_{total}\) 是该周期内执行的全部测试条数,作为分母基线。
    NtotalN_{total}
  3. 占比计算两者相除得到 flaky 在全部测试中的占比,越低越好。
    NflakyNtotal\frac{N_{flaky}}{N_{total}}
  4. 百分比与阈值乘以 100% 后与 3% 的经验线比较,超线就要专项治理。
    ×100%\times 100\%

质量门禁

主厨在出菜口把关:每道菜出锅前必须经过他这一关,色泽、温度、摆盘合格才盖章,不合格当场打回。这个“出菜口盖章”,就是 CI 里的质量门禁:只有测试通过率达到门槛、关键用例全绿,代码才允许合并或发布。

设计质量门禁要定三件事:门槛指标(通过率、关键用例、覆盖率下限)、阻塞规则(不达标就拒绝合并,需要负责人豁免才能放行)、趋势监控(观察近 30 天门禁拒绝率,指标突然恶化说明策略失效)。门禁是工程权衡,不是越严越好。

一条常见的门禁是:总体通过率不低于 99%,且标记为 critical 的用例必须 100% 通过。

主厨把关太松,坏菜端上桌砸招牌;把关太严,后厨怨声载道、偷偷走捷径。所以门禁要分层:关键菜全检,普通菜抽检,再配合豁免记录和趋势看板——让规则被信任,而不是被绕过。

Ppass=NpassNrun≥99%\displaystyle P_{pass} = \frac{N_{pass}}{N_{run}} \geq 99\%
通过率门槛 = 通过用例数 ÷ 运行用例数,必须 ≥ 99%。它相当于主厨定的“出菜合格线”。
  1. 通过用例数\(N_{pass}\) 是本次 CI 运行中真实执行且通过的测试条数。
    NpassN_{pass}
  2. 运行用例数\(N_{run}\) 是本次运行实际执行的测试总条数(缓存跳过的另计)。
    NrunN_{run}
  3. 通过率两者相除得到本次运行的通过率,是门禁判断的直接依据。
    NpassNrun\frac{N_{pass}}{N_{run}}
  4. 门禁阈值\( \geq 99\% \) 表示通过率不能低于 99%,守住这条线才放行。
    ≥99%\geq 99\%

工程实践

餐厅开了半年,要算总账:备菜速度为什么忽快忽慢?瓶颈在洗菜还是炒菜?煮烂的食材成本有多少?工程实践就是 CI 体系的经营复盘——把测试金字塔、并行、缓存、门禁真正管起来。

在 AI 项目里,这套体系要往外延伸,因为被测对象不只是代码,还有模型和数据:模型重新训练后要跑评估指标回归(防止效果回退)、数据管线变更后要做 schema 与分布校验、推理服务升级前要跑性能基线对比。这些都是 AI 项目“新加的菜”。

衡量整个 CI 自动化体系好不好,最核心的 KPI 是反馈时长——从代码提交到拿到测试结果的总时间。

经营复盘最后还要看维护成本:定期清理无效用例、合并重复覆盖、把测试数据版本化。就像主厨定期清点调料、扔掉过期食材——好的 CI 体系不是越建越大,而是越养越精,让每次提交都又快又稳地给出可信结论。

Tfeedback=Tqueue+Texec+Treport\displaystyle T_{feedback} = T_{queue} + T_{exec} + T_{report}
反馈时长 = 排队等待 + 测试执行 + 结果汇总。AI 项目主流水线的目标是把总反馈控制在 10 分钟以内,越快越能支撑高频迭代。
  1. 排队时间\(T_{queue}\) 是代码提交后等待执行资源的时间,受执行器数量与任务负载影响。
    TqueueT_{queue}
  2. 执行时间\(T_{exec}\) 是测试真正运行的时间,受金字塔结构、并行度与缓存命中率共同影响。
    TexecT_{exec}
  3. 报告时间\(T_{report}\) 是结果汇聚、生成报告与产物归档的时间,串行段要刻意压短。
    TreportT_{report}
  4. 求和与目标三段相加就是开发者感知的“提交到结果”全流程,主流水线目标 10 分钟内。
    Tqueue+Texec+TreportT_{queue} + T_{exec} + T_{report}

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 演示

python79 行
"""迷你 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()
目的:模拟一次真实的 CI 流水线——代码只改了 svc_a,因此只有依赖它的测试会被执行(缓存跳过其余),测试用线程池并行跑,通过率不足时触发失败分析与 flaky 重试。
  1. suite = [TestCase("unit_%d" % i, "unit", 0.02) for i in range(10)]生成 10 条毫秒级单元测试,对应金字塔底层
  2. suite.append(TestCase("e2e_order", "e2e", 0.25, flaky=True))E2E 用例少而慢,还故意埋了一条脆断用例
  3. self.deps[t.name] = [services[i % len(services)]]模拟每条测试依赖某个服务文件,作为缓存判断依据
  4. names = [t.name for t in self.tests if set(self.deps[t.name]) & set(changed_files)]变更感知:只挑依赖文件被改动的测试
  5. self.hit_count += len(self.tests) - len(todo)没被选中的测试视为缓存命中并跳过
  6. pool.map(lambda t: t.run(), runnable)用线程池并行执行测试,模拟多执行器分片
  7. if report["rate"] < 1.0:通过率不足时触发失败分析分支
  8. 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. 测试金字塔中各层用例数量的经验比例最接近?

单选题 · easy

  • A.单元七成集成二成 E2E 一成← 正确答案
  • B.单元一成集成二成 E2E 七成
  • C.单元二成集成七成 E2E 一成
  • D.三层用例数量大致均等分布

答案:A。金字塔原则要求底大上小:单元测试毫秒级、成本低,数量最多;E2E 秒级且依赖整套环境,只保留核心链路。就像厨房里逐颗尝番茄(单元)最多,完整试餐(E2E)最少。B 和 C 颠倒了层级分配,D 忽视了成本差异。

Q2. 单元测试最突出的运行特征是什么?

单选题 · easy

  • A.毫秒级完成且用例数量庞大← 正确答案
  • B.分钟级完成且贴近真实用户
  • C.依赖完整数据库与外部服务
  • D.只能由专职测试人员编写

答案:A。单元测试只验证单个函数或类,毫秒级、数量多、成本低,是金字塔的底座,如同主厨逐颗尝番茄。B 是 E2E 的特征,C 是集成或 E2E 的依赖,D 说法错误——开发者日常也在写单元测试。

Q3. 若可并行比例 P=0.8,执行器数 N=4,Amdahl 加速比约为?

单选题 · easy

  • 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. 测试缓存能够合法跳过的前提是什么?

单选题 · mid

  • A.准确判断测试依赖未被变更← 正确答案
  • B.只要流水线变快就大胆跳过
  • C.每周定时清理并全部重跑
  • D.随机挑选一半测试来执行

答案:A。缓存本质是“依赖未变则结果可复用”,前提是变更感知分析准确。调料罐贴了“已验讫”标签,但若有人偷偷换了酱,标签就失效了。B 把速度当目的,C 是定期全量,D 是随机抽样,都会导致漏测。

Q5. 面对一条 flaky 脆断测试,标准动作是什么?

单选题 · mid

  • A.先分类标记再隔离与治理← 正确答案
  • B.每次失败都无脑自动重试
  • C.立即把该测试直接删除
  • D.提高门禁通过率来掩盖

答案:A。flaky 指同一代码多次运行结果不稳定,要先确认它与代码变更无关,再标记、隔离、必要时降级删除。就像厨师手抖导致退菜,要记录是谁在抖,而不是假装没事。B 掩盖问题,C 丢失覆盖率,D 混淆指标。

Q6. 质量门禁设置过严的典型恶果是什么?

单选题 · hard

  • A.开发对抗门禁并绕过流程← 正确答案
  • B.测试覆盖率自动得到提升
  • C.流水线执行成本明显下降
  • D.缓存命中率随之大幅提高

答案:A。门禁是工程权衡,不是越严越好。门槛过高会阻塞合并,团队会以强制合并、删难测用例等方式绕过防线,最终门禁信用崩塌。就像主厨把每盘菜都退回重做,后厨迟早会偷工减料或另寻出路。

Q7. AI 项目中模型评估回归测试的作用是什么?

单选题 · hard

  • A.防止模型变更导致效果回退← 正确答案
  • B.替代全部代码单元测试
  • C.只验证代码能否编译通过
  • D.只能靠人工抽样来评估

答案:A。AI 项目的被测对象不只是代码,还有模型本身。在 CI 中挂黄金评估集,每次模型版本变更自动比对指标,防止“改一处、崩一片”。相当于主厨请试吃员固定试招牌菜保证口味不回退。B 不可替代,C 遗漏模型,D 否认了自动化的可能。

Q8. 关于并行测试的正确做法有哪些?

多选题 · mid

  • A.保证各分片之间完全独立← 正确答案
  • B.对共享资源做好隔离← 正确答案
  • C.执行器数量无限增加
  • D.忽略串行部分的存在

答案:AB。并行测试的核心是独立与隔离:每片有自己的数据与环境,不共享可变状态,如同多位厨师各用各的炉灶。C 错——执行器过多会资源争抢;D 错——串行部分无法消除,只能尽量压缩。

Q9. 失败分析中常见的失败类型有哪些?

多选题 · mid

  • A.代码缺陷导致的确定性失败← 正确答案
  • B.环境异常导致的环境性失败← 正确答案
  • C.结果不稳定的脆断性失败← 正确答案
  • D.缓存命中导致的跳过型失败

答案:ABC。退菜要分三种病因:菜谱错了(确定性,必须修代码)、供应商换了(环境性,修环境或重试)、厨师手抖(flaky,跟踪隔离)。D 是设计内的正常行为,不属于失败类型。

Q10. 测试策略中覆盖率目标应如何制定?

多选题 · extreme

  • A.核心逻辑追求较高的覆盖率← 正确答案
  • B.非核心代码允许适当放松← 正确答案
  • C.所有代码划一要求百分百
  • D.只看行覆盖忽略分支覆盖

答案:AB。覆盖率是校验手段不是目的。核心逻辑(如模型推理、支付)要求 90% 以上,配置与脚本可放宽。C 一刀切会推高成本;D 忽略分支覆盖可能漏掉某个 if 分支的 bug——如同主厨只数尝了几颗番茄,却忘了试两种口味。