做Flutter开发这几年,网络层始终是我最头疼的部分。尤其当项目要适配鸿蒙HarmonyOS的时候,头疼直接变成偏头痛。真实网络环境太不可控了:你要验证一个“请求超时后App有没有友好提示”,总不能每次都去拔网线、关路由器、求后端同事往接口里塞脏数据吧?真这么干,同事大概率会把你拉黑。所以我一直在找一种办法,能在完全脱网的环境下自由控制每个接口的返回行为,把超时、拥塞、脏数据这些异常场景全部模拟出来。
后来我接触到 fake_http_client 这个 Flutter 三方库,发现它就是为这种场景准备的。简单说,它可以在 Flutter 应用内部拦截 HTTP 请求,不修改业务代码,通过配置规则来决定哪些请求走真实网络、哪些直接返回伪造的响应。配合鸿蒙的适配层,它同样能在 HarmonyOS 设备上工作,适合 Flutter 跨端适配、尤其是准备把 Flutter 应用跑到鸿蒙上的团队。这篇文章我从原理讲到实操,包括脱网测试矩阵的搭建、超时/拥塞/脏数据回调的完整规则配置,以及我在鸿蒙设备上踩过的坑。后面内容偏实操,建议边看边打开项目试。
1. 为什么需要 fake_http_client:网络测试的老大难问题
1.1 传统测试为什么这么折磨人
网络测试有几个绕不过去的坎。第一是依赖真实环境。你连的是测试服务器,但测试服务器并不稳定,有时候接口挂了,你以为是自己逻辑错了,查半天发现是后端在发版。第二是异常场景极难复现。超时、断网、服务端返回非 JSON、字段类型错乱,这些情况在联调阶段基本靠运气,你没法对后端说“请把接口返回一个字符串类型的 data 字段,我好测一下前端会不会崩”。第三是代码污染。以前我在业务代码里写过各种 mock 开关,用 if (isMock) return fakeData; 这种逻辑包了一层又一层,等到上线前还得专门做清理,一旦漏删就是生产事故。
这还不是最麻烦的。Flutter 应用一旦跑到鸿蒙 HarmonyOS 上,网络层的问题会被放大。鸿蒙的 Flutter 运行时由社区和厂商共同适配,底层网络栈跟 Android 有差异,DNS 解析、Socket 行为、超时语义都可能有变化。如果还用老办法去测,很可能在 Android 上没问题的代码,到鸿蒙上就各种超时、连接失败。这时候你特别需要一套能够脱离真实网络、在本地精确制造故障的工具链。fake_http_client 就是我在这个背景下找到的最顺手的一个。
1.2 fake_http_client 到底做了什么
你可以把它理解成“进程内的网络拦截代理”。应用发出的每一个 HTTP 请求,都会先经过 fake_http_client 的规则引擎。引擎根据请求的 URL、路径、请求头等特征去匹配你提前注册好的规则。如果匹配成功,它就不会真的把请求发到远端服务器,而是直接在内存里构造一个 Response 返回给业务层;如果匹配失败,请求就继续走原生的 HTTP 客户端发出去。
这种机制跟 Charles、Fiddler 这类抓包工具很像,但最大的区别是它完全在 Dart 层完成,不走系统代理,不依赖 PC 端工具,也不需要在设备上配置证书。也就是说,只要 Flutter 应用能跑起来,fake_http_client 就能在应用内部“偷偷”把网络请求换掉。对鸿蒙这种新生态来说,这个优势很明显:很多代理工具未必完美支持鸿蒙的网络栈,但你在 Dart 层做拦截,适配性和可控制性都高得多。
我第一次用它时,只花了一个下午就把原来需要后端配合的场景全部变成了本地规则,当时心里的感觉就是:这个库我应该早一点遇到。
1.3 “无代码侵入”的真实价值
“无代码侵入”这个词容易被人忽视,但实际价值非常大。传统 mock 方案要侵入业务代码,在每个 API 调用处加判断,或者在 Repository 层包一层 Mock 实现。侵入意味着什么?意味着你在测试的是“一个被改过的应用”,而不是“即将上线的应用”。有些 bug 恰恰只在真实代码路径上出现,你一旦在调用链中间插了一层 if,原来的错误处理逻辑可能就被绕过了。
fake_http_client 的做法是在底层拦截,业务代码完全感觉不到。它对业务层暴露出来的还是一个正常的 HTTP 响应,只不过这个响应是假的。也就是说,你测试的是完整的生产代码路径:统一的网络层封装、序列化、异常捕获、UI 状态管理,全都真实跑了一遍。这样测出来的结果才有参考价值。尤其是鸿蒙适配阶段,你特别需要确认 Flutter 业务代码在鸿蒙上的表现跟 Android 是否一致,这种无侵入的 mock 方式就能帮你把变量控制到最小。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 鸿蒙 HarmonyOS 适配的关键差异与准备
2.1 鸿蒙的网络栈跟 Android 到底哪不一样
很多 Flutter 开发者刚接触鸿蒙时,会默认“鸿蒙能跑 Android 的 Flutter 代码,网络也应该一样”,这是错觉。鸿蒙 NEXT 已经不再兼容 Android APK,Flutter 应用在鸿蒙上是通过 OpenHarmony 的 Flutter 适配层运行的。底层 Socket、DNS、HTTP 栈走的都是鸿蒙自身的网络能力,而不是 Android 那套。
实际影响有几个。第一是网络权限:HarmonyOS 需要在 module.json5 中显式声明 ohos.permission.INTERNET,漏了这个权限,所有网络请求都会失败,但错误信息很可能只是笼统的“Connection refused”或者“SocketException”,排查起来很费劲。第二是 HTTP 明文流量策略:鸿蒙对明文 HTTP 请求、自签名证书的限制和 Android 不一定相同,你在本地 mock 的接口如果用了 http://10.0.2.2 这类地址,行为可能和在 Android 模拟器上不一样。第三是代理设置:鸿蒙系统级代理跟 Android 的 Fetch API 行为有差异,而且 fake_http_client 是在 Dart 层做拦截,它的工作方式不依赖系统代理,反而更稳定。
我在项目里就遇到过一个问题:同一个 Flutter 应用,在 Android 上跑网络正常,在鸿蒙上却频繁超时。最后发现不是代码问题,而是鸿蒙设备上网络权限没有配置好,请求全部被系统拦了。所以做鸿蒙网络测试之前,先把权限和明文流量配置检查一遍,能省不少时间。
2.2 依赖安装与初始化顺序
使用 fake_http_client 本身不复杂。在 pubspec.yaml 里加依赖,然后执行 flutter pub get。这里提醒一句,Flutter SDK 版本要尽量新,鸿蒙适配通常要求 Flutter 3.7 以上,建议直接用当前最新的稳定分支。如果你还没配置好 Flutter 环境,先去官网下载对应系统的 SDK,配置好 flutter doctor,再拉到鸿蒙工程的构建环境。这一步是基础,跳过的话后面会遇到很多奇怪的问题。
yaml复制dependencies:
flutter:
sdk: flutter
fake_http_client: ^1.0.0
初始化顺序很有讲究。它必须在应用发起第一个网络请求之前完成。我习惯放在 main() 函数的最前面,也就是 runApp() 之前。原因很容易理解:如果请求已经发出去了,拦截引擎还没有初始化,那这个请求就漏出去了。对于 Flutter 应用来说,很多插件可能在 runApp() 之后很快就有网络操作,所以越早初始化越安全。
dart复制void main() {
WidgetsFlutterBinding.ensureInitialized();
FakeHttpClient.enableLog(true);
FakeHttpClient.globalConfig = FakeConfig(
enabled: true,
rules: [
// 这里放你的匹配规则
],
);
runApp(const MyApp());
}
代码里的 FakeHttpClient.enableLog 是我常用的调试开关。它会把每个被拦截的请求都打印到控制台,包括命中规则、返回了什么样的数据、耗时多久。检查规则是否生效时格外有用。
2.3 网段适配:Host、域名与端口映射的坑
“网段适配”这四个字听起来有点抽象,放到实际场景里其实就是你的 mock 规则要能匹配不同测试环境下的目标地址。我在做鸿蒙适配时,发现最容易踩坑的就是地址写法。
第一个坑是 localhost。在 Android 模拟器上,localhost 指向模拟器自己;在 iOS 模拟器上,它通常指向宿主机;到了鸿蒙真机上,localhost 又是真机自己。所以你规则里如果写死 localhost:8080,很可能压根匹配不到真实请求。解决方案是规则里不要依赖具体的 device 环境,而是统一匹配路径前缀或域名通配符。
第二个坑是内网网段。测试环境往往有多个部署节点,比如 192.168.1.10、192.168.1.11、10.20.2.130。为了模拟不同的网络环境,我并不希望把所有 IP 都改成同一个 mock 地址,而是希望保留原 Host,只把响应内容替换掉。fake_http_client 的规则匹配可以做得很灵活,用通配符或者正则匹配一组地址。例如:
dart复制FakeRule(
match: (request) {
final host = request.url.host;
final path = request.url.path;
return (host.endsWith('.internal.test') || host.startsWith('192.168.'))
&& path.startsWith('/api/');
},
response: FakeResponse.json({
'code': 0,
'data': {'message': 'mock response'},
}),
)
这样无论请求打到哪个测试网段的 IP,只要能匹配上规则,就会被拦截。如果你的应用里用了多个环境,比如预发、灰度、生产,你只需要在匹配逻辑里加一个环境判断,让 mock 规则只作用于测试网段,生产网段保持真实请求,这就非常安全了。我个人的偏好是:mock 规则永远只对测试域名开放,绝不匹配线上域名,防止手滑把线上请求也拦掉。
3. 脱网测试矩阵设计:覆盖超时、拥塞与脏数据
3.1 测试矩阵的维度拆解
搭建脱网测试矩阵,核心不是写规则,而是先把你要测的场景理清楚。我一般把维度拆成四个:请求生命周期阶段、故障类型、数据规模、触发条件。
请求生命周期阶段指的是连接建立、请求发送、响应读取这三个阶段。故障类型则对应超时、拥塞、数据错误三大类。数据规模分小数据、正常数据、超大报文、空数据。触发条件可以按接口维度、用户维度、随机概率维度来设计。把这几个维度组合起来,就形成了一个矩阵。比如“订单列表接口 + 响应读取阶段超时 + 1KB 响应 + 每次都触发”,就是一个非常明确的测试用例。
实际项目中完全不需要把矩阵做得很庞大。我通常只挑四类高优先级场景:一类是请求超时,用于验证重试和友好提示逻辑;一类是响应缓慢,用于验证 loading 状态和用户体验;一类是返回脏数据,用于验证序列化容错;还有一类是断网后恢复,用于验证客户端状态同步。只要把这四类场景做成规则模板,就能覆盖大部分测试场景。后面的操作都以这四类为核心展开。
3.2 模拟超时:connect、request、read 三阶段要分开玩
很多人以为设置超时就是让接口等很久不返回,其实不够。超时发生在不同阶段,代码路径完全不同。连接超时发生在 TCP 建连阶段,请求超时发生在整个请求生命周期,读超时发生在服务端已经接受请求但迟迟不返回响应体的时候。这三个阶段在 Flutter 的 HttpClient 里有各自独立的配置,在 fake_http_client 里也可以分别控制。
我之前遇到过一个很隐蔽的 bug:订单状态页在弱网环境下偶尔会卡死,很久才弹错误提示。后来分析发现,是服务端接受连接后不返回数据,客户端读超时时间设置过长。这种问题你用单纯的“延迟返回”很难复现,因为延迟返回最终还是会返回数据,但真实的读超时场景是连接一直挂着,没有任何响应。所以测试矩阵里一定要区分这两种行为。
fake_http_client 模拟连接超时,最直接的方式是让请求直接抛出 SocketException,模拟 DNS 解析失败或连接被拒绝。模拟读超时,则是让规则匹配成功后不返回任何响应,一直挂到客户端自己的超时阈值触发。这两者的代码路径完全不一样。
dart复制// 模拟连接失败:直接抛异常
FakeRule(
match: (request) => request.url.path.startsWith('/api/connect'),
errorFactory: (request) => SocketException('Connection refused'),
);
// 模拟读超时:挂起请求,直到客户端超时
FakeRule(
match: (request) => request.url.path.startsWith('/api/read-timeout'),
behavior: FakeBehavior.hang(),
);
建议你把三条规则分别测一遍,再组合成一个复杂的 mock 配置,这样才能真正验证应用对超时环节的处理是否完备。
3.3 模拟拥塞:让接口慢得恰到好处
拥塞和超时不一样。超时是“得不到响应”,拥塞是“响应很慢但最终会到”。在真实网络里,拥塞往往伴随着随机性:这一次 200 毫秒,下一次 5 秒,偶尔还会掉几个字节。要模拟这种效果,fake_http_client 最方便的方式是给规则配置延迟,而且延迟要让它是可变的,不能固定值。
我常用的策略有三种。第一种是固定延迟,适合测试 UI 状态流转,确保 loading 展示逻辑正确;第二种是随机延迟,在一个区间内随机选一个值,适合模拟真实弱网;第三种是延迟加上错误概率,比如 20% 的请求直接失败,80% 的请求延迟返回,这能最大限度还原网络抖动。
拥塞的另一种表现是限速。服务端返回数据不是一次性到位的,而是慢慢吐出来。你在 mock 层可以模拟这种分块传输:先返回一部分数据,停顿一下,再把剩余数据返回。这种方式对测试流式解析、进度条展示特别有效。
dart复制FakeRule(
match: (request) => request.url.path.startsWith('/api/order/list'),
behavior: FakeBehavior.chunked(
chunks: [
Chunk(delay: Duration(milliseconds: 500), bytes: '[{"id":1,'),
Chunk(delay: Duration(milliseconds: 800), bytes: '"name":"test"}]'),
],
),
)
这个例子模拟了一个被拆成两段的 JSON 响应。注意,这种分块返回很容易测出代码里不规范的 JSON 拼接问题,值得每个团队都跑一遍。
3.4 模拟脏数据:前端容错的照妖镜
脏数据回调是网络测试里最容易被忽略,但最能暴露问题的一环。服务端正常情况下会返回结构清晰的 JSON,但真实世界里,某个字段可能为空、可能类型错乱、可能多了一层嵌套、可能返回的压根不是 JSON。这些脏数据一旦到客户端,轻则字段显示异常,重则直接 crash。
用 fake_http_client 模拟脏数据,最直接的方式就是手工构造各种不合法的响应体。我一般建一个“脏数据仓库”,里面放一批专门的响应模板。比如订单接口正常返回 {"code":0,"data":{"orderId":"123","amount":99.9}},脏数据就返回 {"code":0,"data":{"orderId":123,"amount":"99.9"}},把字段类型完全反过来;或者返回 {"code":0,"data":null} 这种空对象,测试空值处理;再或者直接返回一段非 JSON 文本,比如 <html>403 Forbidden</html>。
dart复制FakeRule(
match: (request) => request.url.path.startsWith('/api/dirty'),
response: FakeResponse.raw(
statusCode: 200,
body: 'not_json_at_all',
headers: {'content-type': 'application/json'},
),
)
上面这个规则比较狠:状态码是 200,content-type 是 JSON,但 body 是一段普通文本。很多应用的网络层只判断 statusCode,不去真正校验 body 是否合法,这种情况就会出错。把这种规则加入测试矩阵后,你很快就能发现前端解析逻辑到底够不够健壮。
4. 实操过程:无代码侵入搭建被拦截的测试环境
4.1 规则引擎初始化:入口越早越好
前面提到初始化要放在 runApp() 之前,这里再补充一个工程化的细节。随着测试场景变多,你不可能把所有规则都塞在 main() 里,那样代码会膨胀到没法维护。我建议把规则拆成独立文件,通过一个工具类加载,并提供多套预设配置。比如 MockProfile.light 只 mock 成功响应,MockProfile.crash 模拟各种异常,MockProfile.chaos 随机命中各种故障,这样换一套测试场景只需要改一个枚举值。
dart复制enum MockProfile { disabled, light, chaos, dirty }
MockProfile _currentProfile = MockProfile.disabled;
void loadMockProfile(MockProfile profile) {
_currentProfile = profile;
FakeHttpClient.globalConfig = buildConfig(profile);
}
在测试阶段,可以通过环境变量、启动参数或者代码接口来切换 profile。项目里我会放一个隐藏的调试入口,只在 debug 模式编译进去,release 包绝对不会包含 mock 逻辑。这样既保证了脱网测试的灵活性,又不会污染线上包。
4.2 按网段配置路由规则
实际配置规则时,重点在 Host 和 path 的匹配设计。通常我会把目标系统拆成模块,比如 user、order、payment 三个模块,每个模块对应一组路径前缀,然后针对每个模块配置不同的 mock 行为。这样做的好处是你能精细控制:我可以在用户模块保持真实请求,订单模块返回超时,支付模块返回脏数据,模拟一个“部分服务正在故障”的场景。
dart复制final userRules = [
FakeRule(
match: (request) => request.url.path.startsWith('/api/user'),
response: FakeResponse.json({
'code': 0,
'data': {'id': 10001, 'name': 'mock_user'},
}),
),
];
final orderRules = [
FakeRule(
match: (request) =>
request.url.path.startsWith('/api/order') &&
request.url.host.endsWith('internal.test'),
behavior: FakeBehavior.hang(),
),
];
final rules = [...userRules, ...orderRules];
这里有一点要注意:规则的顺序会影响结果。如果两条规则都匹配同一个请求,我建议按“具体优先”来排列,先放路径更精确的规则,再放宽泛的兜底规则。否则一个宽泛规则命中了所有请求,后面的精确规则就不会被执行,排查起来会一头雾水。
域名匹配还要考虑大小写。HTTP 的 host 是不区分大小写的,但如果你用字符串精确匹配去对 hppt://ABC.Test.com,很可能匹配不到。我一般会先 toLowerCase() 再比较,或者直接用正则写 (?i) 忽略大小写。
4.3 在鸿蒙设备上验证拦截是否生效
规则配置好以后,验证工作也很关键。光盯着日志看可能不够,我通常用三步确认:
第一步,先关闭 WiFi,保证设备处于脱网状态。然后启动应用,如果应用里的数据还能加载出来,说明走的是 mock 数据;如果界面一直空转并报网络错误,说明请求没有被拦截,需要回去查规则。这个方法最简单粗暴,也最有效。
第二步,打开 fake_http_client 的日志开关,查看控制台输出。它会打印出每个请求的命中情况。如果发现请求没有命中任何规则,你就知道是规则写得不匹配;如果命中了但返回数据不对,你就要去看 response 配置有没有问题。
第三步,在规则里故意让某个接口返回一个明显的特征值,比如 "mock_hello"。如果页面上出现这个字符串,说明拦截链路完全通畅。这一步特别适合排查“为什么界面显示的是老数据”这种缓存问题,因为 mock 数据特征明显,一眼就能认出来。
4.4 与 CI/自动化测试联动
脱网测试的好处在于可重复,把规则固化后就能接入自动化。我项目里的做法是在 integration_test 里定义一组用例,每个用例启动 App 时传入不同的 mock profile,验证特定的异常场景。比如用例 A 验证超时提示,用例 B 验证脏数据不崩溃,用例 C 验证断网恢复。
bash复制flutter test integration_test/network_stability_test.dart \
--dart-define=MOCK_PROFILE=chaos
通过 --dart-define 传递环境变量,在 App 启动时读取并加载对应的 profile,就能在同一套代码下跑出不同故障组合。CI 里我建了几个 job,分别跑成功响应、超时、脏数据三个场景,如果有问题直接定位到对应 job。这样团队每次提测前都会自动跑一遍脱网测试矩阵,很多回归问题在提测阶段就被拦住了。
5. 常见问题与排查技巧实录
5.1 拦截规则不生效,请求还是发出去了
这是最常见的坑。先检查初始化时机,确认 fake_http_client 是否已经在 runApp() 之前完成。如果你的 App 里有某个插件在 main() 很早期就发起请求,很可能在规则注册前就已经走了真实网络。
第二步检查匹配逻辑。尤其注意 path 是否带前导斜杠、query 参数是否影响了匹配。有些请求是带 query 的,比如 /api/order?id=1,你匹配规则如果只判断 path 没管 query,是不会受影响的;但如果有些库喜欢把 path 拼上 query,你的 path.endsWith('/api/order') 可能就会失败。稳妥的办法是只匹配 request.url.path,而不是 request.url.toString()。
第三步检查是不是走了原生网络栈。fake_http_client 只能拦截 Dart 层发起的 HTTP 请求。如果你的业务里有原生插件直接发起网络请求,绕过了 Flutter 的 HttpClient,那这个库是拦不住的。遇到这种情况,要么改掉那部分逻辑,要么把它单独拎出来做 mock 适配。
5.2 超时模拟不精准,延迟时间对不上
模拟超时的时候,很多人直接给规则配置一个固定延迟,比如 10 秒。但你会发现,客户端设置的连接超时是 5 秒,结果实际触发却在 10 秒以后。原因是延迟模拟的是“响应很慢”,不是“连接失败”。如果你想要的是客户端在 5 秒后触发超时逻辑,应该用 hang 或者抛异常的方式,而不是延迟返回。
另外,Flutter 里默认的超时时间可能跟你自己设置的不一致。比如 Dio 有 connectTimeout 和 receiveTimeout,但底层 HttpClient 也有自己的超时语义。测试时最好把两边的超时日志都打开,看清楚到底是哪一层抛出的超时。我踩过坑:Dio 的 connectTimeout 设了 5 秒,但底层 HttpClient.connectionTimeout 没有设置,导致某些异常情况下,系统默认超时时间反而先触发了,让定位问题多花了好几个小时。
5.3 脏数据导致崩溃,却不知道是哪段代码崩的
脏数据最容易暴露序列化层的薄弱点,但也最难定位,因为崩溃栈很多时候指向的是 Dart 内置的 JSON 解析库,而不是你的业务代码。我的经验是:在 mock 层故意抛错之前,先给网络封装层加上全局异常捕获,记录原始响应体和当前请求路径,这样崩溃发生时能看到是哪条规则、哪个接口触发的。
dart复制FakeHttpClient.onUnhandledRequest = (request, error) {
debugPrint('Mock rule failed: ${request.url} error: $error');
};
还有一个实用技巧:不要一上来就模拟最复杂的脏数据。先从单一字段类型错误开始,比如把 int 换成 String,确认这个字段能正常处理;然后逐渐增加复杂程度,比如字段缺失、数组嵌套错误、空前缀、超大字符串。一层层往上叠加,定位问题会清晰很多。如果最开始就返回一个完全随机的 JSON 结构,崩溃以后根本分不清是解析库的问题还是业务逻辑的问题。
5.4 鸿蒙专属问题:权限、明文流量与证书差异
在鸿蒙上跑这套测试矩阵,有几个专属问题要提前预防。第一是网络权限,module.json5 里必须要有 ohos.permission.INTERNET,否则所有请求都会被系统拦掉。第二是明文流量,如果你的 mock 接口走的是 http:// 明文协议,鸿蒙默认可能限制访问。你需要在鸿蒙工程里配置网络安全策略,允许测试域名的明文流量,或者直接在 mock 规则里把响应伪装成 HTTPS 请求来绕过这个问题。注意这里的绕过是技术配置,不要涉及其他含义。
第三是证书信任。鸿蒙系统对自签名证书、私有证书的信任策略跟 Android 不一样。如果你用了 Charles 之类的工具抓包,会发现鸿蒙设备上证书信任配置比较麻烦。但 fake_http_client 的好处是它在 Dart 层直接返回 Response,不需要经过真实网络传输,所以不会触发证书校验问题。这也是我推荐它在鸿蒙适配期使用的原因之一。
下面是我整理的一份快速排查表,出现问题时可以直接对照:
| 问题表现 | 可能原因 | 解决办法 |
|---|---|---|
| 所有请求都失败 | module.json5 缺少 INTERNET 权限 | 添加权限声明后重新编译 |
| 请求发出但没有命中 mock | 规则匹配了 url.toString() 而非 path | 改用 request.url.path 匹配 |
| 只有部分设备不生效 | localhost/IP 指向不同 | 统一用域名通配符或环境判断 |
| 超时时间不准 | 底层和上层超时配置不一致 | 统一设置 Flutter HttpClient 超时 |
| mock 数据导致崩溃 | 序列化层缺少类型校验 | 增加原始响应日志和全局捕获 |
| 自动测试偶尔失败 | 随机延迟导致用例超时 | 在 CI 里加大等待阈值或关掉随机性 |
6. 最后的实操心得:把模拟规则当成团队资产来维护
最后分享一点个人体会。fake_http_client 这类工具的价值,其实不在于它能拦截请求,而在于它逼着你把网络异常场景拆解成可复用的规则。第一批规则通常是应急写的,跑通就行;等到你维护了一段时间,会慢慢沉淀出一套“网络故障模式库”:某个接口常见的问题是什么、哪种错误最容易出现、线上曾经出过什么故障。把这些模式固化到 mock 配置里,成为团队共同的测试资产,收益会越来越大。
我的做法是每个迭代结束后,把线上发现的问题补进规则库。比如线上出现过一次“订单金额返回字符串导致支付页展示错误”,我就会在 dirty profile 里新增一条规则,把这问题固化成回归用例。这样一来,以后每次发版前跑一遍 mock 测试矩阵,就等于把历史上所有网络层踩过的坑重新点了一遍名。虽然 fake_http_client 不一定是你团队最终选型的库,但这种“故障经验代码化”的思路,任何项目都值得借鉴。
