跳至主内容
版本:当前版本

测试概述

非官方测试版翻译

本页面由 PageTurner AI 翻译(测试版)。未经项目官方认可。 发现错误? 报告问题 →

随着代码库规模扩大,未曾预料的小错误和边界情况可能引发连锁故障。程序缺陷会导致糟糕的用户体验,最终造成业务损失。防止这种隐患的方法之一就是在发布前对代码进行充分测试。

本指南将介绍多种自动化测试方法,涵盖从静态分析到端到端测试的全流程,确保你的应用行为符合预期。

Testing is a cycle of fixing, testing, and either passing to release or failing back into testing.

为何需要测试​

人非圣贤孰能无过。测试的重要性在于它能帮你发现潜在错误并验证代码功能。更重要的是,当你添加新功能、重构现有代码或升级项目依赖时,测试能确保系统持续稳定运行。

测试的价值远超预期。修复代码缺陷的最高效方式,就是编写一个能暴露该问题的失败测试用例。修复后重新运行测试,若通过则表明缺陷已彻底解决且不会重现。

测试用例还能为新团队成员提供活文档。初次接触代码库的开发者通过阅读测试,能快速理解现有代码的运行机制。

Last but not least, more automated testing means less time spent with manual QA, freeing up valuable time.

静态分析​

提升代码质量的第一步是采用静态分析工具。这类工具能在不运行代码的前提下,实时检测编写过程中的错误。

  • Linters 通过分析代码捕捉常见问题(如未使用代码),规避潜在陷阱,并标记违反风格指南的行为(例如配置要求使用空格时误用制表符)。

  • 类型检查 确保传入函数的参数符合定义,例如防止向需要数字参数的计数函数传递字符串。

React Native 内置了两大工具:ESLint 负责代码规范检查,TypeScript 提供类型校验功能。

编写可测试的代码​

编写可测试代码是测试的前提。以飞机制造为例:在整机试飞验证复杂系统协作前,每个部件都需独立测试。机翼需进行极限负载弯曲测试,发动机零件需验证耐用性,挡风玻璃需通过模拟鸟撞试验。

软件开发同理。相较于将整个程序塞进单一巨型文件,将代码拆分为小型模块能进行更彻底的测试。因此,编写可测试代码与构建清晰、模块化的代码结构密不可分。

提升应用可测试性的关键在于:将视图层(React 组件)与业务逻辑及应用状态(无论使用 Redux、MobX 或其他方案)解耦。这样既能独立测试不依赖组件的业务逻辑,又能让组件专注核心职责——渲染用户界面。

理想情况下,应将所有逻辑和数据获取移出组件。此时组件仅负责渲染,状态完全独立于组件存在,应用逻辑甚至可在脱离 React 组件的环境下运行!

技巧

建议您通过其他学习资源深入探索可测试代码的编写技巧。

编写测试​

编写完可测试代码后,是时候编写实际测试了!React Native 的默认模板内置了 Jest 测试框架。它包含针对此环境预设的配置,无需立即调整配置和模拟(稍后会详细介绍模拟)。你可以使用 Jest 编写本指南涉及的所有测试类型。

备注

如果你采用测试驱动开发(TDD),实际上你会先编写测试!这样就能确保代码天然具备可测试性。

组织测试结构​

测试应保持简短,且最好只验证单一功能。以下是用 Jest 编写的单元测试示例:

js
it('given a date in the past, colorForDueDate() returns red', () => {
expect(colorForDueDate('2000-10-20')).toBe('red');
});

测试通过传递给 it 函数的字符串描述。请精心编写描述以明确测试目标,确保涵盖:

  1. 给定条件(Given)—— 前置状态

  2. 执行操作(When)—— 被测函数的具体行为

  3. 预期结果(Then)—— 期望的输出

这也称为 AAA 模式(准备 Arrange,执行 Act,断言 Assert)。

Jest 提供 describe 函数帮助组织测试。用 describe 将相同功能的测试分组,支持嵌套使用。常用函数还有 beforeEach 或 beforeAll,用于初始化测试对象。详见 Jest API 文档。

若测试步骤或断言过多,建议拆分为多个小测试。确保测试完全独立:每个测试必须能单独运行,且测试套件中任一测试的执行结果不影响其他测试。

开发者通常期望代码完美运行,但测试场景恰恰相反——请将测试失败视为_好事_!失败往往意味着存在问题,这使你有机会在影响用户前修复它。

单元测试​

单元测试覆盖代码最小单元,如独立函数或类。

当被测对象存在依赖项时,通常需要模拟(mock)它们,详见下文。

单元测试的优势在于编写和运行速度快,能即时反馈测试结果。Jest 还提供 Watch 模式,可实时运行与编辑代码相关的测试。

模拟(Mocking)​

当被测对象依赖外部模块时,常需通过“模拟”将其替换为自定义实现。

信息

通常测试中使用真实对象优于模拟,但某些场景不可行。例如:JS 单元测试依赖 Java/Objective-C 编写的原生模块时。

假设你开发的城市天气应用依赖外部天气服务。若服务返回“下雨”,需显示雨云图标。测试中不应直接调用该服务,因为:

  • 网络请求会导致测试变慢且不稳定

  • 每次运行测试可能返回不同数据

  • 第三方服务可能会在你急需运行测试时掉线!

因此,你可以提供该服务的模拟实现(mock),从而有效替代数千行代码和那些联网的温度计!

备注

Jest 内置了完善的模拟功能支持,从函数级别到模块级别的模拟均可实现。

集成测试​

在构建大型软件系统时,其各个组件需要相互协作。在单元测试中,如果某个单元依赖其他组件,你最终可能需要通过模拟(mock)来创建虚假依赖。

集成测试会将真实的独立单元(如同在生产应用中)组合起来进行联合测试,确保它们的协同工作符合预期。这并不意味着完全不需要模拟:你仍可能需要模拟某些依赖(例如模拟天气服务的通信),但所需模拟量远少于单元测试。

信息

请注意"集成测试"的术语定义存在差异。单元测试与集成测试的界限有时并不明确。本指南采用以下判定标准:

  • 组合多个应用模块(如上所述)
  • 使用外部系统
  • 发起网络请求(如天气服务API)
  • 执行文件或数据库I/O操作

组件测试​

React 组件负责渲染应用界面,用户将直接与其输出交互。即使应用的业务逻辑测试覆盖完备且正确,缺乏组件测试仍可能导致向用户交付损坏的 UI。组件测试可归为单元测试或集成测试,但由于其在 React Native 中的核心地位,我们将单独讨论。

测试 React 组件时,通常关注两个方面:

  • 交互行为:确保用户操作时组件响应正确(例如用户点击按钮)

  • 渲染输出:确保 React 使用的渲染结果准确(例如按钮的视觉呈现和布局位置)

例如,若某个按钮设置了 onPress 监听器,你既要测试其视觉呈现正确,也要验证点击事件能被组件正确处理。

推荐使用以下测试库:

  • React Native Testing Library 基于 React 测试渲染器构建,提供下文所述的 fireEvent 和 query API

  • [已弃用] React 官方的 Test Renderer 可将组件渲染为纯 JavaScript 对象,无需依赖 DOM 或原生环境

警告

组件测试仅在Node.js环境中运行JavaScript代码,不涉及iOS/Android等平台的底层原生代码。因此无法100%保证用户端功能完整性——若原生代码存在缺陷,此类测试无法捕获。

用户交互测试​

除渲染 UI 外,组件还需处理如 TextInput 的 onChangeText 或 Button 的 onPress 等事件,其中可能包含各类函数和回调。参考以下示例:

tsx
function GroceryShoppingList() {
const [groceryItem, setGroceryItem] = useState('');
const [items, setItems] = useState<string[]>([]);

const addNewItemToShoppingList = useCallback(() => {
setItems([groceryItem, ...items]);
setGroceryItem('');
}, [groceryItem, items]);

return (
<>
<TextInput
value={groceryItem}
placeholder="Enter grocery item"
onChangeText={text => setGroceryItem(text)}
/>
<Button
title="Add the item to list"
onPress={addNewItemToShoppingList}
/>
{items.map(item => (
<Text key={item}>{item}</Text>
))}
</>
);
}

测试用户交互时,应从用户视角出发:界面呈现什么?交互后发生什么变化?

基本原则:优先使用用户可感知的元素进行验证:

反之应避免:

  • 对组件的 props 或 state 进行断言

  • testID 查询

避免测试实现细节,例如 props 或 state——虽然这类测试可行,但它们并不以用户如何与组件交互为导向,并且在重构时容易失效(例如,当你想要重命名某些内容或使用 Hook 重写类组件时)。

信息

React 类组件尤其容易暴露实现细节(如内部状态、属性或事件处理程序)的测试。为避免测试实现细节,建议使用带有 Hooks 的函数组件,这使得依赖组件内部细节变得更加困难。

React Native Testing Library 等组件测试库通过精心设计的 API,帮助开发者编写以用户为中心的测试。以下示例使用 fireEvent 的 changeText 和 press 方法模拟用户交互,并通过查询函数 getAllByText 在渲染输出中查找匹配的 Text 节点。

tsx
test('given empty GroceryShoppingList, user can add an item to it', () => {
const {getByPlaceholderText, getByText, getAllByText} = render(
<GroceryShoppingList />,
);

fireEvent.changeText(
getByPlaceholderText('Enter grocery item'),
'banana',
);
fireEvent.press(getByText('Add the item to list'));

const bananaElements = getAllByText('banana');
expect(bananaElements).toHaveLength(1); // expect 'banana' to be on the list
});

这个示例并非测试调用函数时状态如何变化,而是测试当用户在 TextInput 中修改文本并点击 Button 时会发生什么!

测试渲染输出​

快照测试是 Jest 提供的高级测试方法。作为强大但底层的工具,使用时需要格外谨慎。

"组件快照"是由 Jest 内置的自定义 React 序列化器生成的类 JSX 字符串。该序列化器将 React 组件树转换为人类可读的字符串——换句话说,快照是测试运行期间生成的组件渲染输出的文本表示,可能如下所示:

tsx
<Text
style={
Object {
"fontSize": 20,
"textAlign": "center",
}
}>
Welcome to React Native!
</Text>

快照测试通常先实现组件再运行测试,生成快照后将其作为参考快照保存在仓库文件中。该文件需提交并通过代码审查。后续任何渲染输出的改动都会导致快照变化,使测试失败。此时需更新参考快照并通过提交和审查流程。

快照测试存在几个弱点:

  • 开发者或审查者难以判断快照变化是预期改动还是错误证据,大型快照尤其难以理解且价值有限

  • 快照创建时即被视为正确,即使渲染输出实际存在错误

  • 快照失败时,开发者可能未经充分审查就使用 --updateSnapshot 选项更新快照,这需要严格的开发纪律

快照本身不能确保组件渲染逻辑正确,其主要价值在于防止意外变更,并验证被测 React 树中的组件是否接收到预期 props(如样式等)。

我们建议仅使用小型快照(参见 no-large-snapshots 规则)。若需测试 React 组件两种状态间的差异,推荐使用 snapshot-diff。不确定时,优先采用前文所述的显式断言方法。

端到端测试​

端到端(E2E)测试从用户视角验证应用在设备(或模拟器/仿真器)上的实际表现。

这需要以发布模式构建应用并运行测试。E2E 测试不再关注 React 组件、React Native API、Redux store 或业务逻辑——这些既非 E2E 测试目的,在测试过程中也无法访问。

相反,端到端(E2E)测试库允许你在应用界面中查找并操控元素:例如,你可以像真实用户那样_实际_点击按钮或在 TextInputs 中输入文字。随后你可以断言特定元素是否存在于应用界面中、是否可见、包含什么文本内容等。

端到端测试能为你提供最高级别的信心保障,但代价包括:

  • 编写耗时比其他测试类型更长

  • 运行速度较慢

  • 更容易出现不稳定性("不稳定测试"指随机通过或失败,且代码未作任何更改)

建议对应用的关键路径进行端到端测试覆盖:认证流程、核心功能、支付模块等。非关键路径可使用更快的 JavaScript 测试。测试越多信心越足,但维护和执行时间也会增加。请根据实际需求权衡利弊。

现有多种端到端测试工具可选:在 React Native 社区中,Detox 因其专为 React Native 设计而广受欢迎;其他适用于 iOS/Android 的流行方案包括 Appium 和 Maestro。

总结​

希望本指南让你有所收获。应用测试方法多样,初期选择或显困难。但相信当你开始为 React Native 应用添加测试时,一切都会变得清晰。还在等什么?赶快提升测试覆盖率吧!

相关链接​


本指南由 Vojtech Novak 原创并完整贡献。