测试替身与服务虚拟化

共 19 题
📑 题目列表 19 题
#
★★★

1. Mock、Stub、Fake、Spy、Dummy 五种测试替身的本质区别和使用场景?请为每种替身举一个典型应用示例。

Mock、Stub、Fake、Spy、Dummy 五种测试替身的本质区别和使用场景是什么?请为每种替身举一个典型应用示例?

  • 五种替身的定义与本质区别
  • 各替身的典型应用场景
  • 用示例说明替身选择的依据

五种测试替身按"是否真实实现、是否验证交互"区分。Dummy 是最轻的替身,仅用于占位传递参数,从不被真正使用,典型如传入一个不需用到的空对象。Stub 提供预置的返回数据,用于"喂数据"让被测单元走完分支,不验证交互,典型如网络接口返回固定 JSON。Fake 有真实可用的简略实现,能跑通业务逻辑但用轻量替代(如内存 Map 代替数据库),典型如内存版 Repository。Mock 既预置行为又验证交互是否发生(如 verify 某个方法被调用且参数正确),用于验证"协作是否正确"。Spy 包装真实对象,既走真实逻辑又记录调用,典型如包裹真实 HttpClient 并断言其被调用次数。本质区别在于:Dummy 不参与、Stub/Fake 只供数据、Mock/Spy 验证行为,其中 Spy 保留真实实现而 Mock 完全不依赖真实实现。

选择替身的关键是"测试关注什么":只关心返回数据用 Stub/Fake,关心调用关系用 Mock/Spy,仅占位用 Dummy。过度使用 Mock 会耦合实现细节,能真实地带数据走逻辑时优先用 Fake/Stub。

// Dummy:占位,不参与逻辑
PaymentService svc = new PaymentService(new DummyLogger());
// Stub:返回固定数据
when(api.getUser(1)).thenReturn(user("alice"));
// Spy:包装真实对象,记录调用
List<String> spy = spy(new ArrayList<>());
spy.add("a");
verify(spy).add("a");
#
★★★

2. Stub、Spy、Mock、Fake 四类测试替身在行为验证上的本质区别与误用场景?

Stub、Spy、Mock、Fake 四类测试替身在行为验证上的本质区别与误用场景是什么?

  • 行为验证 vs 状态验证的区别
  • 四类替身在验证上的定位差异
  • 常见误用场景

行为验证是"断言对象间发生了预期的交互",状态验证是"断言被测对象最终的输出/状态"。Stub 预先配置返回值,不验证行为,只支持状态验证;Spy 包装真实对象并记录调用,默认走真实逻辑,可额外做行为验证;Mock 预设行为并典型用于行为验证(verify);Fake 是真实可用的简略实现,完全不参与交互验证,只供状态验证。误用场景主要包括:把 Stub 当 Mock 用(配了返回值却去 verify,导致桩行为与验证重叠、意图混乱);用 Mock 验证一切内部调用(过度 mocks 导致测试与实现细节耦合,重构即碎);对无副作用的方法用 Spy 记录调用(无意义);把 Fake 塞进复杂协作测试却依赖其内部细节(把测试写成了实现)。正确做法是遵循"能状态验证就不用行为验证,行为验证只在协作正确性本身就是被测点时使用"。

行为验证是把测试钉在"对象如何协作"上,更脆弱、更贴近实现;状态验证更贴近用户可观察结果。因此应优先状态验证,仅在确需校验协作契约(如回调、事件、顺序)时才用 Mock/Spy 做行为验证。

#
★★★

3. Mock 过多导致的"实现耦合测试"如何识别与重构,如何以契约测试替代?

Mock 过多导致的"实现耦合测试"如何识别与重构?如何以契约测试替代?

  • 实现耦合测试的识别信号
  • 重构策略
  • 契约测试作为替代方案

"实现耦合测试"指测试因 Mock 预设了内部方法的调用细节而紧贴实现,改动内部实现即失败。识别信号包括:测试断言了私有行为的调用顺序、为每个内部方法都配了 mock、重构时大量测试同时红、测试名与实现方法名一一对应、mock 链过长。重构思路:一是把"行为验证"改为"状态验证",只断言被测单元的输出;二是把"协作验证"收敛到边界接口处,不在内部每个类上 mock;三是用 Fake 替代 Mock 提供真实感受的依赖;四是当跨团队/跨进程协作时,用契约测试(Consumer-Driven Contract,如 Pact)替代 Mock——消费者定义契约,生产者验证契约,双方都从真实契约生成测试替身,保证替身行为与真实 API 一致,从而既解耦又防漂移。契约测试的价值是把"测试内 mock 的行为"提升为"团队间共享的契约",从源头消除实现耦合。

核心是"让测试面向稳定的契约而非脆弱的实现"。Mock 适合单元内部协作,契约测试适合跨进程/跨团队边界;把对内部实现的 mock 替换为对契约的验证,既降低耦合又提升真实性。

#
★★

4. 服务虚拟化(Service Virtualization)在微服务测试中的价值,与 Mock/Stub 的关系和差异?

服务虚拟化在微服务测试中的价值是什么?它与 Mock/Stub 的关系和差异是什么?

  • 服务虚拟化的定义与价值
  • 与 Mock/Stub 的关系
  • 虚拟化与编码替身的差异

服务虚拟化(Service Virtualization)是把真实下游系统(如第三方支付、银行、消息队列)虚拟为可编程、可重复、可管控的模拟服务,用于测试、演示与联调。其价值在于:解除对真实下游的依赖(真实环境不稳定、计费、不可用)、加速 CI(无需等待真实服务)、支持创建真实环境难以构造的异常场景(慢响应、超时、错误码)。与 Mock/Stub 的关系:Mock/Stub 是代码层面的替身,通常嵌入被测进程;服务虚拟化是"进程级/网络级"的替身,运行在独立进程,通过录制-回放或脚本模拟网络协议与响应。差异在于:Virtualization 更接近真实网络边界(真实 URL、真实协议、可配置延迟/故障),可共享给多个团队,管理更集中;而 Mock/Stub 更轻、内嵌、快速,适合单元级。两者是互补关系:单元用 Mock/Stub,服务间集成用 Service Virtualization。

服务虚拟化的本质是把"替身"从代码层提升到"服务层"——它模拟的是整条网络服务而非单个对象,因此能覆盖真实网络交互、并发与故障注入,是微服务集成测试与联调的关键工具。

#
★★

5. WireMock/Hoverfly 在服务虚拟化中的典型模式,请求匹配策略(URL/Header/Body)、有状态场景模拟(Stateful Scenarios)和延迟注入的设计?

WireMock/Hoverfly 在服务虚拟化中的典型模式有哪些?请求匹配策略、有状态场景模拟和延迟注入如何设计?

  • 请求匹配策略(URL/Header/Body)
  • 有状态场景模拟(Stateful Scenarios)
  • 延迟注入与故障注入

WireMock 的请求匹配策略可通过 URL 路径、查询参数、Header、Body(含 JSON 路径表达式)、Cookie 等组合匹配,优先级从"精确匹配"到"通配/正则"逐级降级,实现"同一接口不同入参返回不同响应"。有状态场景模拟通过 Scenario 机制:先定义场景初始状态,通过"已状态化"的 stub 在收到特定请求后推进到下一状态并返回对应响应,从而模拟"先创建后查询"这类依赖顺序的流程。延迟注入通过 fixedDelay 或 uniform/random 延迟模拟慢接口,再结合故障注入(返回 500、超时、畸形响应)测试下游容错。Hoverfly 类似,额外擅长录制-回放与流量仿真。设计要点是:把"无状态 stub"与"有状态场景"分开管理,用命名清楚的场景标识状态转换,避免状态机失控。

这些模式让虚拟服务逼近真实——匹配粒度决定可控性,状态机决定流程真实性,延迟/故障注入决定容错验证能力。合理组合三者可覆盖绝大多数下游交互场景。

// WireMock:按 Body 匹配 + 延迟注入
stubFor(post(urlEqualTo("/pay"))
    .withRequestBody(matchingJsonPath("$.amount", equalTo("100")))
    .willReturn(aResponse().withFixedDelay(2000).withStatus(200).withBody("{\"ok\":true}")));
#
★★

6. 契约感知(Contract-Aware)的 Mock 生成,如何基于 OpenAPI/AsyncAPI 规范自动生成测试替身,确保 Mock 行为与真实服务一致?

契约感知的 Mock 生成如何实现?如何基于 OpenAPI/AsyncAPI 规范自动生成测试替身,确保 Mock 行为与真实服务一致?

  • 契约感知 Mock 的概念
  • OpenAPI/AsyncAPI 生成替身的机制
  • 一致性保障

契约感知的 Mock 生成是指以服务契约(OpenAPI/Swagger 描述 REST,AsyncAPI 描述异步事件)为唯一事实来源,自动生成与真实服务行为一致的测试替身。实现方式:解析 OpenAPI 规范得到端点、请求/响应 Schema、枚举与示例值,据此生成"同意该契约都能应答"的 stub——请求校验通过则返回符合 Schema 的示例响应,未定义则返回 4xx/5xx。对 AsyncAPI 场景,则基于事件 Schema 生成消息并模拟消息队列行为。这样替身行为由契约驱动,天然与真实服务一致,杜绝人工手写 stub 的漂移。为保证一致性,可配合"契约校验"(每次真实服务变更后比对契约,CI 中自动失败)与"契约双方测试"(消费者测契约、生产者测实现),确保规范与实现同步。工具如 prism、spring cloud contract、schema 校验都能支撑。

核心是把"Mock 内容来自人写"变为"Mock 内容来自契约"——契约是双方共同遵守的接口约定,据此生成替身自然一致,也把"测试内 mock"升级为"团队共享契约"。

#
★★

7. 事件驱动系统(Event-Driven Systems)中的测试替身,如何模拟消息队列(Kafka/RabbitMQ)的行为?事件顺序、重复投递和消费者组的测试策略?

事件驱动系统中的测试替身如何设计?如何模拟消息队列行为?事件顺序、重复投递和消费者组的测试策略是什么?

  • 消息队列模拟方式
  • 事件顺序与重复投递测试
  • 消费者组测试策略

事件驱动系统中测试替身要模拟消息队列的投递语义。模拟方式分三层:单元层用内存版假队列(Fake)或封装队列的 Mock Client;集成层用 Testcontainers 启动真实 Kafka/RabbitMQ 容器(最真实);服务虚拟化层用 WireMock/Hoverfly 模拟队列协议。测试策略需覆盖三类关键语义:事件顺序——用带 partition 的队列验证同一 key 的事件按序消费,测试中可注入乱序事件验证幂等与重排;重复投递——模拟 at-least-once 语义,重复发同一事件验证消费者幂等处理(不重复记账、不重复外发);消费者组——验证同一组内分区再平衡、不同组都能收到同一事件、组内消息不重复消费。设计中要显式控制 auto.offset.resetenable.auto.commit 等参数,用可控的 offset 与分区键构造确定性场景。

事件驱动测试的难点在于"无共享调用栈、异步、有投递语义"。策略是:用真实队列容器保证语义真实,用可控的投递脚本覆盖顺序/重复/组再平衡等边界,并在测试中显式等待消费完成的信号(如消费计数)而非固定 sleep。

#
★★

8. 服务虚拟化(WireMock/Mountebank)与真实下游联调环境的取舍,虚拟化度如何度量?

服务虚拟化与真实下游联调环境如何取舍?虚拟化度如何度量?

  • 虚拟化 vs 真实联调环境的取舍
  • 虚拟化度的度量
  • 分层策略

服务虚拟化与真实下游联调各有取舍。虚拟化的优势:稳定、快速、可控、可构造异常、成本低、不依赖第三方可用性;劣势:可能存在替身与真实行为漂移、覆盖不到真实协议细节。真实联调的优势:真实性最高、能发现契约与集成问题;劣势:依赖外部可用性、慢、不可控、计费、构造异常困难。取舍原则是"分层组合":单元/集成测试用虚拟化保障快速稳定,定期排期(如每周/每版本)打开真实下游联调验证契约与真实行为,形成"日常虚拟化 + 定期真实联调"的互补。虚拟化度(Virtualization Coverage)度量方式:被虚拟化的下游服务数 / 总下游服务数,以及"虚拟化覆盖的接口/场景比例";更精确可用"真实联调触发的接口数 / 全部接口数"反向度量。目标是在保证快速反馈的同时,保留足够真实联调以暴露漂移。

取舍的核心是"反馈速度 vs 真实性"的平衡。虚拟化度是治理指标,但并非越高越好——过高会掩盖真实集成问题,过低则拖慢反馈。应结合契约测试对冲漂移,用定期真实联调补齐真实度。

#
★★

9. AI 生成单元测试时,替身选择错误(如把真实依赖当 Mock)如何通过代码评审发现?

AI 生成单元测试时,替身选择错误(如把真实依赖当 Mock)如何通过代码评审发现?

  • AI 生成测试的替身选择风险
  • 代码评审中识别替身错误的方法
  • 评审清单与纠正手段

AI 生成单元测试时常见的替身选择错误包括:把真实依赖(真实数据库、真实 HTTP 服务)直接当 Mock 用导致测试慢且不可控;对无副作用方法用 Mock 验证交互;对真实实现用 Spy 却断言了内部细节;用 Dummy 占位却期望它参与逻辑。代码评审中的识别方法:一是特征检查——查看测试是否访问真实网络/DB/文件系统(new HttpClient()new JdbcTemplate() 等),是否违反"单元测试不触 IO";二是断言审查——看 verify 的对象是否本可直接用真实实现或 Fake 走状态验证;三是运行观察——测试是否慢、是否依赖外部环境、是否在 CI 中 flaky。纠正手段:对无外部依赖(纯逻辑)的方法用真实实现或 Fake;对跨边界协作用契约化的 Mock;建立"替身选择"评审清单并纳入 AI 生成模板,让 AI 输出时默认使用 Fake/Stub 而非连真实服务。

关键是把"替身选择"作为评审的显式检查项。AI 生成测试常因"图省事"直接把真实依赖当 Mock,评审者需通过"是否触 IO、是否 verify 真实对象、是否依赖外部"三个信号快速拦截,并用模板约束 AI 输出。

#
★★

10. Mockito/pytest-mock/Vitest 等框架中 when/stub 与 verify 的职责边界,何时断言交互、何时只 stub 返回值?

Mockito/pytest-mock/Vitest 等框架中 when/stub 与 verify 的职责边界是什么?何时断言交互、何时只 stub 返回值?

  • when/stub 与 verify 的职责分工
  • 何时断言交互,何时只 stub
  • 评测原则

在 Mockito、pytest-mock、Vitest 等框架中,when(...).thenReturn(...)(或 mock.someMethod())用于"stub 返回值",即替代方法实现,让被测单元得到确定数据;verify(...) 用于"断言交互",即验证方法是否被调用、调用了多少次、参数是否正确。职责边界是:stub 回答"依赖返回什么",verify 回答"被测单元是否正确地调用了依赖"。何时断言交互:当协作调用本身是被测点(如回调触发、事件发送、外部服务调用、按条件发送通知)时,用 verify 验证"是否调用、参数、次数、顺序"。何时只 stub:当被测重点是被测单元自身的逻辑/输出,依赖只是提供数据时,只 stub 返回值、做状态验证,绝不 verify 内部调用。原则是"能状态验证就不行为验证;行为验证只在协作正确性即被测点时使用"。

边界混乱是过度 mock 的根源。把 verify 用在使用 Stub 就够的地方,会把测试钉死在实现细节上;把 stub 用在本该验证交互的地方,则漏掉关键行为。正确区分的关键是被测点的性质。

// 只 stub:被测重点是计算逻辑
when(priceRepo.getPrice("A")).thenReturn(10);
assertEquals(11, service.checkout("A"));   // 状态验证
// 断言交互:发送通知是被测行为
verify(notifier).send(anyString(), eq("alice"));  // 行为验证
#
★★

11. 测试替身的层级选择,对象级(Mock 对象)、进程级(WireMock/Mountebank)与容器级(Testcontainers)各自的适用场景?

测试替身的层级选择如何做?对象级、进程级与容器级替身各自的适用场景是什么?

  • 三级替身的定义
  • 各层级适用场景
  • 层级的取舍原则

测试替身按"模拟的裁切面"分三层:对象级(Mock 对象、Fake)在进程内替换单个对象,最轻最快,用于单元测试的快速反馈;进程级(WireMock、Mountebank、Hoverfly)在独立进程用网络协议模拟整条服务,用于集成测试与服务虚拟化,能覆盖真实网络边界与故障注入;容器级(Testcontainers)启动真实中间件容器(数据库、Kafka、Redis、Elasticsearch),最接近真实环境,用于集成测试验证真实语义与兼容性。适用原则是"按测试目标选择最近的真":单元测试用对象级保证速度,集成测试用容器级保证真实,跨服务/第三方依赖用进程级虚拟化保证可控。三者在同一测试金字塔中配合:单元层用 Mock/Stub,集成层用 Testcontainers,端到端用真实服务。

层级选择本质是"速度/资源成本 vs 真实性"的平衡。越往上层越真实越贵,越往下层越轻越快。正确做法是根据测试类型选择恰当裁切,避免"用容器级替单元依赖"拖慢反馈,也避免"用对象级替集成依赖"导致假绿。

#
★★

12. 测试替身与测试金字塔的配合,单元层用 Mock/Stub、集成层用 Testcontainers、端到端用真实服务,各层替身策略如何避免"重单元轻集成"?

测试替身与测试金字塔如何配合?各层替身策略如何避免"重单元轻集成"?

  • 测试金字塔与替身策略的对应
  • "重单元轻集成"的成因与危害
  • 平衡策略

测试金字塔要求"底层单元测试多、中层集成测试适中、顶层端到端少"。对应的替身策略:单元层用 Mock/Stub 隔离依赖、快速验证逻辑;集成层用 Testcontainers/进程级虚拟化验证真实协作;端到端用真实服务验证完整链路。"重单元轻集成"指单元测试大量但集成测试缺失,成因是单元测试容易写、集成测试准备复杂、环境不稳定,导致团队不敢写集成测试。其危害是:替身与真实行为漂移未被发现(假绿)、集成契约问题到生产才暴露。避免策略:一是把集成测试列为 CI 门禁,用 Testcontainers 消除环境搭建成本,让集成测试像单元测试一样好跑;二是用契约测试对跨边界进行验证,弥补集成测试缺口;三是控制各层数量比例并设立"每层最小覆盖"的度量,防止单层失衡。

金字塔的核心是"速度与覆盖的平衡",而替身策略是落实金字塔的手段。避免"重单元轻集成"的关键是降低集成测试的门槛(Testcontainers、契约测试、环境自动化),而不是简单增加数量。

#
★★

13. 替身与真实实现的漂移治理,替身行为与真实 API 不一致导致假绿,如何用契约测试(Pact)与定时真实联调对冲?

替身与真实实现的漂移如何治理?替身行为与真实 API 不一致导致假绿,如何用契约测试(Pact)与定时真实联调对冲?

  • 替身漂移与假绿的成因
  • Pact 契约测试的机制
  • 定时真实联调的对冲

替身漂移指替身行为与真实 API 不一致(如响应字段、状态码、时序变化),导致测试在替身上通过、在真实环境失败(假绿)。治理手段一是契约测试(Pact):消费者端定义"期望的交互契约",生成 pact 文件;提供者端对该契约做验证,确保真实实现满足契约。任何一方变更导致契约不满足,CI 即失败,从而在替身使用前就发现漂移。治理手段二是定时真实联调:即使有契约,也定期(按版本/每周)把系统切到真实下游跑一遍关键链路,验证真实行为与替身假设一致。两者对冲:契约测试保证"契约层面一致",真实联调保证"真实行为层面一致"。此外,替身生成尽量基于契约(Contract-Aware)而非手写,从源头减少漂移。

假绿的根源是"替身代表的是历史假设而非当下真实"。Pact 把假设固化为可验证契约,真实联调兜底验证真实行为,两者结合形成"契约保证 + 真实兜底"的双保险。

#
★★

14. 测试替身框架的实现原理,Mockito 如何用字节码/动态代理生成替身,final 类与方法为何难 mock(inline mock maker)?

测试替身框架的实现原理是什么?Mockito 如何用字节码/动态代理生成替身?final 类与方法为何难 mock?

  • Mockito 生成替身的机制
  • 动态代理与字节码
  • final 类难 mock 的原因与 inline mock maker

Mockito 生成替身依赖两种机制:一是 JDK 动态代理(Proxy),用于接口型 mock,通过生成实现接口的代理类,把方法调用转发给拦截器;二是字节码增强(ByteBuddy 生成子类),用于类型 mock,通过生成被测类的子类并重写方法,在调用处记录 stub 行为与 verify 信息。final 类与方法难 mock 的原因:final 类不能被继承、final 方法不能被重写,因此传统的"子类化 + 重写"机制无法生效。为此 Mockito 提供 inline mock maker(基于 JVM 的 Instrumentation API,改写类字节码本身而非生成子类),从而能 mock final 类与 final 方法;在新版本中 inline mock maker 已成为默认。代价是 inline 方式对 JVM 启动有额外开销,且受 Java 模块/signature 权限影响。理解原理有助于选择 mock 策略(接口优先、或用可注入设计而非硬 mock final)。

理解实现原理能解释"为什么接口优先、组合优先的设计更易测"。Mockito 靠子类化/代理,设计上避免 final 能降低对字节码工具的依赖,也让测试更自然。

#

15. 为什么"为新需求写 Mock"经常是危险信号?

为什么"为新需求写 Mock"经常是危险信号?

  • 新需求写 Mock 的风险
  • 背后反映的设计问题
  • 正确做法

新需求引入时,若第一件事是"为新需求写 Mock",往往说明该需求无法用真实对象/现有依赖直接驱动,背后可能反映三类问题:一是耦合过重——新逻辑重度依赖内部实现细节,难以用真实协作验证;二是边界不清——需求没有清晰的输入输出,只能靠 Mock 内部调用"喂"出结果;三是测试一开始就验证实现而非行为。Mock 本质是"模拟外部依赖",新需求若正确设计,核心逻辑应通过构造函数/参数注入依赖、用 Fake/Stub 提供数据即可驱动,无需为"内部调用"写 Mock。危险信号的另一层含义是:新需求本应是"添加新行为",却要求大量 mock 才能测,说明需求被设计成"修改内部调用"而非"增加可观察行为",可能是过度设计或职责不清。正确做法是:先看能否用真实逻辑/Fake 测,仅在确实跨越边界时才 Mock,并优先断言状态而非交互。

危险的根源是"Mock 用来掩盖不可测性,而非测试可测性"。新需求写 Mock 往往意味着逻辑被埋在调用链里,正确做法是重构使逻辑可测,让测试面向行为而非实现。

#

16. 过度 Mock 的反模式识别,哪些信号表明测试替身使用过度(如 Mock 链过长、Mock 与实现强耦合、测试通过但集成失败)?如何重构?

过度 Mock 的反模式如何识别?哪些信号表明测试替身使用过度?如何重构?

  • 过度 Mock 的识别信号
  • 反模式的危害
  • 重构策略

过度 Mock 的识别信号包括:Mock 链过长(mock 一个对象,其内部又 mock 出多个对象,层层包裹);Mock 与实现强耦合(改动内部方法名/参数即碎一片测试);测试通过但集成失败(替身行为与真实不符,假绿);测试名与实现方法一一对应(钉死在实现);为每个对象都建 mock、verify 大量内部调用。重构策略:一是面向"稳定输出"断言,把行为验证转为状态验证;二是用 Fake/Stub 替代 Mock,让依赖真实走逻辑;三是把跨进程协作收敛到边界接口,用契约测试替代内部 mock;四是把过长 mock 链拆分为可注入的协作对象,暴露真实依赖;五是删除冗余的 verify。重构目标是让测试"测行为不测实现",降低耦合与维护成本。

过度 Mock 的根因是"用 mock 补不上糟糕的设计"。重构思路是让代码更可测(依赖注入、边界清晰),让测试更贴近真实行为,从而减少对 mock 的依赖。

#

17. 测试替身在并发与性能测试中的适用边界是什么?

测试替身在并发与性能测试中的适用边界是什么?

  • 替身在并发测试中的作用
  • 替身与性能测试的冲突
  • 边界把握

测试替身在并发测试中可用于隔离并发逻辑的依赖(如用内存 Fake 替代数据库,验证并发下的竞态与锁),但有边界:并发问题常见于"真实共享资源"的语义(连接池、事务隔离、分布式锁),替身会掩盖这些真实语义,导致并发测试通过而生产并发失败。在性能测试中替身几乎不适用——因为性能测试要测的是真实系统在真实负载下的吞吐/延迟,替身会换成真实开销,结果失真;替身仅可用于"性能基准隔离"(测被测单元自身 CPU 开销)或"压下游性能"时屏蔽真实下游。适用的边界是:用替身验证"逻辑正确性"(并发下的状态一致性),但验证"真实资源语义"与"性能指标"必须用真实依赖。性能测试中替身的分支是"录制-回放"或"虚拟化"模拟下游,但需明确标注其非真实性能。

核心是"替身换掉的是真实语义,因此凡验证真实语义的场景(并发资源、性能开销)都不该用替身"。并发与性能测试的替身适用边界是"逻辑正确性"而非"真实指标"。

#

18. 测试替身带来的「假绿」问题,替身行为与真实实现漂移导致测试通过但生产失败,如何用契约测试与定期联调对冲?

测试替身带来的"假绿"问题如何解决?替身行为与真实实现漂移导致测试通过但生产失败,如何用契约测试与定期联调对冲?

  • 假绿的概念与成因
  • 契约测试与定期联调的作用
  • 综合治理

"假绿"指测试在替身上通过,但真实环境失败。根源是替身行为与真实实现漂移,比如下游响应字段新增/改名、状态码变化、超时行为不同,替身还在用旧假设。对冲手段:契约测试(Pact)把"消费者期望的交互"固化为契约文件,生产者验证通过后才算契约满足,任何漂移在 CI 即暴露,防止替身与真实不一致;定期真实联调则把系统真实切到下游跑关键链路,验证真实行为与替身假设一致,兜底契约测试覆盖不到的真实细节。实践中应"替身生成基于契约"(Contract-Aware),降低手写漂移;同时定期(按版本/周期)打开真实联调,并建立"替身 vs 真实"的差异报告。三重组合:契约保证一致性、真实联调兜底真实行为、监控记录漂移。

假绿的根因是"替身是历史假设,而真实是当下现实"。契约测试解决"契约漂移",真实联调解决"行为漂移",两者缺一不可,是防假绿的双保险。

#

19. 服务虚拟化的录制-回放模式,录制真实下游响应生成虚拟服务,如何保证录制数据的脱敏与时效性?

服务虚拟化的录制-回放模式如何工作?如何保证录制数据的脱敏与时效性?

  • 录制-回放模式
  • 数据脱敏
  • 时效性治理

录制-回放模式是:先对真实下游发起请求,把真实响应(含请求匹配信息)录制下来,生成虚拟服务(如 WireMock 的 stub 文件、Hoverfly 的仿真),回放时按请求匹配返回录制响应。它比手写 stub 更真实、成本低。但录制数据有两大风险:脱敏——录制响应可能包含真实用户 PII、手机号、卡号、token 等敏感信息,存储与回放会泄露,需在录制后做脱敏(正则替换手机号/卡号、掩码 token、替换身份证/邮箱),并校验脱敏后不破坏约定结构;时效性——录制数据会过期,下游接口升级后录制响应与真实不符,需定期重新录制(按版本/周期触发)、对录制响应做老化标记(recording timestamp)、结合契约校验判断是否失效,并建立"录制 → 校验 → 上架"的流程。实践中把录制数据作为版本化资产,带元数据(来源环境、录制时间)管理,定期刷新。

录制-回放的代价是"真实但可能过期、真实但可能敏感"。脱敏解决合规,时效性解决一致性,两者是录制模式能否长期使用的关键配套治理。