本节摘要:移动端测试要在「快」与「真」之间分层布防:Jest 单测管逻辑(毫秒级、海量跑),组件测试管界面契约(模拟原生层、验证交互),Detox 端到端管真机旅程(真实双端、核心链路)。本节讲清三层的分工与编写要点,给出移动语境特有的模拟对象清单(原生模块、网络、存储),并用一次发布前的回归实战串起全流程。测试不是成本,是把 8.1 的产物「敢发出去」的底气。
经典测试金字塔在移动端要翻译成 RN 的话。底层:逻辑单测——纯函数、状态归约器(4.1 的 useReducer 是绝佳的被测物)、数据加工层,用 Jest 跑,毫秒级反馈,海量化生长。中层:组件测试——渲染一个组件、模拟点击、断言界面输出,原生组件与样式在测试环境里被模拟成可断言的替身,牺牲真实性换取速度。顶层:端到端测试——Detox 驱动真机或模拟器上的真实应用,像用户一样点击输入,验证完整旅程(登录、下单、支付)。三层的关系是「速度换真实」:越往上越慢越真实,所以底层要厚、顶层要精——端到端用例只留给核心链路,跑在每次发布前,而不是每个提交后。
一个被测物充足的单测样例,看状态归约器如何被表格化穷举:
// cartReducer.test.js:4.1 的购物车归约器,纯函数最适合穷举 import cartReducer from './cartReducer'; describe('购物车归约器', () => { const item = { id: 'p1', name: '示例商品', price: 9.9 }; test('空车加入商品变为一件', () => { const next = cartReducer({ items: [] }, { type: 'ADD_ITEM', item }); expect(next.items).toHaveLength(1); expect(next.items[0].count).toBe(1); }); test('重复加入同款数量累加而非新增条目', () => { const one = cartReducer({ items: [] }, { type: 'ADD_ITEM', item }); const two = cartReducer(one, { type: 'ADD_ITEM', item }); expect(two.items).toHaveLength(1); expect(two.items[0].count).toBe(2); }); test('清空返回空车且不引用旧数组', () => { const one = cartReducer({ items: [] }, { type: 'ADD_ITEM', item }); const cleared = cartReducer(one, { type: 'CLEAR' }); expect(cleared.items).toHaveLength(0); expect(one.items).not.toHaveLength(0); // 原状态未被篡改(不可变纪律) }); });
单测与组件测试的成败在模拟。RN 项目必配的四类替身:原生模块——桥接调用在测试环境不存在,统一为模块写模拟实现(返回约定值、记录调用),7.1 的接口声明此时第二次显价值:合同明确,替身就写得标准。网络层——请求库配测试适配器返回预置响应,顺带覆盖慢响应与失败分支(4.3 的竞态用例在这层就能单测复现)。存储层——持久化接口换成内存实现,「随写随存」纪律的测试版:断言「写入发生在状态变化的当下」而不是退出时。导航——组件测试里把导航对象替换为记录函数,断言「跳转目标与参数」,5.2 的参数纪律又有了机器守门员。四类替身共用一条设计原则:只在边界处模拟,业务内部不 mock——否则测试锁死的是实现细节,重构一次全盘红。
端到端测试工具不少,Detox 对 RN 的适配最顺——它是「灰盒」思路:测试进程与应用进程协作,知道页面何时在忙(动画、加载),同步机制天然规避「元素还没渲染就去找」的脆弱问题。用例的写法接近自然语言:
// e2e/login.spec.js:核心链路的端到端用例 describe('登录与下单主链路', () => { it('游客登录到下单完成', async () => { await element(by.id('home-banner')).tap(); // 用可测试ID定位,别用文案 await element(by.id('login-button')).tap(); await element(by.id('phone-input')).typeText('13800000000'); await element(by.id('code-input')).typeText('246810'); await element(by.id('submit-button')).tap(); await waitFor(element(by.id('home-ready'))) .toBeVisible().withTimeout(5000); // 同步等待就绪标志 await element(by.id('buy-now')).tap(); await expect(element(by.id('order-success'))).toBeVisible(); }); });
两条端到端专用纪律:其一,定位一律用「可测试标识」而非界面文案——改个按钮文字不该弄碎测试;其二,用例间状态隔离,每个用例从确定的种子状态出发,否则用例顺序成了隐性依赖。双端执行上,Detox 分别驱动两端构建(复用 8.1 的构建配置),核心链路必须双端都绿才算过——「iOS 过了 Android 没跑」的测试等于没测,这是本册反复出现的双端纪律在测试层的落点。
背景:版本含三处改动——支付流程重构、列表性能优化、权限流程改造(7.3 案例),发布前要给出一揽子结论。操作:单测先行,全量跑通且要求核心模块行覆盖不降(归约器与数据层最容易在重构中悄悄变行为);组件测试覆盖三处改动的界面契约,权限钩子的各分支状态机被逐一断言;端到端只跑四条核心链路(登录、下单支付、扫码、推送点击),双端真机矩阵各过一遍;最后加测 8.1 说的产物自检与弱网场景(网络模拟到三档信号)。结果:端到端在 Android 侧抓住一处支付回调的时序问题——组件层单测全绿、真机才暴露,恰好演示了三层的分工与不可替代性。解读:测试策略的精髓是「每层只信它能证明的事」:单测证明逻辑对、组件证明契约稳、端到端证明旅程通,发布决策看齐三层证据。变式:测试金字塔在紧急热修场景要降级执行——热更只跑单测加受影响模块的组件测试加一条核心端到端,全量回归留给正式版本,速度与安全按发布类型动态平衡。
测试兜了底,下一节把产物送出门:签名、上架,以及那条合规的热更新快速通道。