原文链接:www.callstack.com/blog/testin…
作者:Ferran Negre
在我的React开发历程中,我始终坚持测试并鼓励身边的人测试他们的应用。我一直使用Jest进行测试。但让我们暂且搁置这个话题,聚焦于React Native。如果能用同一个框架来测试我们出色的移动应用,岂不是美妙至极?更进一步……如果我们在Web应用和移动应用间复用逻辑,那么复用部分测试代码岂不更妙?
接下来系列文章将尝试解答这些问题。本文首篇将聚焦React Native组件的测试实践,并对比分析Jest新推出的快照系统与浅渲染比较(测试工具包及Enzyme)的优劣之处。
为何称其为新版Jest?
在询问我为何称其为新版Jest之前,请先查阅Jest 14与Jest 15的更新日志。不仅是我认为Jest与一年前相比焕然一新...其头号核心贡献者也如此评价:
没错,它与旧版Jest毫无关联。仅保留了名称和标识。
——Christoph Nakazawa (@cpojer) 2016年9月2日
本系列将聚焦Jest的新特性之一:快照测试。
Jest能捕捉React树或其他可序列化值的快照,助你快速编写测试,并提供无缝的更新体验。
现在就让我们验证上述引文!
在 React Native 项目中配置 Jest
真的超级简单。只需按照文档操作:
完成。立即尝试:
测试组件的时机
本文示例仓库渲染了一份Github仓库列表。其中包含多个测试示例,但本文将聚焦于测试名为RepoItem的简单组件:
完整文件包含属性定义和样式。
该组件有一个名为isSelected的特性属性。当isSelected=true时,该项将以绿色背景渲染。
Shallow渲染比较中的问题
你是否曾使用过 react-addons-test-utils 或 Enzyme 提供的Shallow渲染功能?它们非常相似,Enzyme 之所以流行,是因为它拥有更优雅的 API,还能让你在 React 树中定位元素。让我们选择这个工具,尝试用它来测试 React Native 组件。
Enzyme
尝试使用Enzyme的浅函数时遇到的首个问题(暂不考虑react-native中的mount):
react-addons-test-utils是支持react@0.13至14版本的隐式依赖项。请在 devDependencies 中添加对应版本。详见 github.com/airbnb/enzy…
react-dom 是隐式依赖项,用于支持 react@0.13–14。请在 devDependencies 中添加对应版本。详见 github.com/airbnb/enzy…
npm install react-dom react-addons-test-util --save-dev
因此我们实际上需要在开发依赖项中安装 react-dom 和 react-addons-test-util 才能让 Enzyme 正常工作……哎呀!
好,现在来尝试进行第一次完整的浅层比较:
onPress 函数会导致这个测试失败。
实际上,这个初始示例无法正常工作。看到onPress属性了吗?首要问题在于组件中它的定义方式:
两个函数实例并不相同(我们创建了新函数实例),因此比较必然失败。虽然可以设计变通方案,但...让我们偷个懒跳过TouchableHighlight部分,假设没人会破坏它:
这样虽然能运行,但...
难道不觉得我们在重复实现render()吗?每次添加/移除属性或修改逻辑...都得跑回来做同样的事。
好的,现在我们需要测试当 isSelected 属性设置为 true 时的组件行为:
复制粘贴复制粘贴…
由于重建完整场景过于重复,开发者往往只测试动态部分,例如:
没错,你可以创建一个函数获取浅层组件并接收 isSelected 属性
这样似乎更合理?继续往下看…
假设项目进行到某个阶段,客户要求点击列表项时,除了显示选中背景色外,还需展示更多信息——具体来说是仓库描述。当 isSelected = true 时,该项应以绿色背景渲染并显示仓库描述
此变更将引发两项后果:
- 检查全部渲染输出的测试将失败:我们需要回溯并重新实现该测试
- 仅检查动态部分的测试仍会通过(哎呀!这算好事吗?)
第一种情况需返工操作:将此行代码复制粘贴至对应位置:
当 isSelected 属性为 true 时
第二种情况需注意:需返回测试用例添加新测试场景:
新增场景
看出问题了吗?我认为...全局视图(完整渲染)的创建复杂、维护繁琐且高度重复(复制粘贴)。第二种方案(使用find、contains等)不够稳健:必须确保实现所有用例,因为新增用例不会破坏现有测试。
请查阅完整的RepoItem-Shallow-test.js文件以获取全局视图。
浅层渲染总结
优点
- 强大的选择器API(find、contains等)
- 在React Native中实际有效(浅层部分)
缺点
- 总感觉在重复编写相同代码,因此人们通常只测试输出(渲染)的局部而非整体
- 组件与测试之间频繁复制粘贴
- 难以维护
去试试说服人们编写这些测试吧
快照测试来拯救
Jest 14 引入了快照测试:看看它们为何如此出色:
消除测试不稳定性:由于测试在命令行运行器中执行,而非真实浏览器或手机上运行,测试运行器无需等待构建、启动浏览器、加载页面并驱动 UI 将组件置于预期状态——这些环节往往导致测试结果不稳定且噪声较大。
快速迭代:工程师期望在1秒内获取结果,而非等待数分钟甚至数小时。若测试运行速度如多数端到端框架般缓慢,工程师往往直接放弃运行甚至不愿编写测试。
调试便利性:在JS集成测试中,直接步入代码调试远比重现截图测试场景、在视觉差异中排查问题高效得多。
读完本文后,让我们亲身体验,用实践验证其价值。
我们可以为第一个 RepoItem 编写快照测试(当 isSelected 设置为 false 时):
请在 RepoItem-Snapshot-test.snap 中检查结果。你会发现它未使用背景色属性(符合预期),因为 isSelected 为 false:
现在,让我们测试当 isSelected 设置为 true 时的输出(此时需要渲染绿色背景)。由于组件本质上是纯函数,这变得轻而易举:
在生成的快照文件 RepoItem-Snapshot-test.snap 中,我们能找到预期部分:
太棒了!现在请记住,客户要求我们实现展开状态以显示仓库描述(背景色之外的部分)。让我们运行测试。
哎呀!有项测试失败了。
让我们来看看原因。在开发阶段,Jest会提供非常有用的错误输出(可通过watch参数实时查看)。由于这次变更属于预期行为,我们可以运行:
这将自动更新我们的快照。请看:
咚!测试失败了,我们检查后发现这是预期结果,并重新生成了快照。我们无需修改测试代码。更重要的是,这个快照差异将在下次提交拉取请求时自动显示。
真的就这么简单。我们的渲染输出已针对两种情况完成全面测试,能够对变更做出响应,提供清晰的错误输出并提供便捷的修复途径。相当酷吧?
能否测试具有内部状态的组件?
在您提问之前,答案是肯定的。请查看Jest仓库中的这个示例:Link.react-test.js。当然,如果您的组件具有内部状态(副作用),测试过程会变得复杂得多。参见:
我缺少类似Enzyme的选择器API
特别是当组件包含状态且需要触发事件时,在树形结构中定位节点会非常繁琐。不过Jest官方已在问题跟踪给出正式答复:
我们正在为测试渲染器开发选择器API,届时将通过官方接口实现此功能——Christoph Pojer
快照总结
优点
- 易于编写
- 避免重复编写(DRY原则)
- 兼容React Native:查看RepoList-Snapshot-test.js及其快照(该快照渲染包含项的ListView——试着不用快照实现这个效果)
- 开发时提供错误信息(可使用—watch t00)
- 更多功能即将推出!
缺点
- 缺少类似Enzyme的选择器API(但如前所述Jest社区正在开发中)
- 快照文件可能庞大,查找目标内容时易受冗余信息干扰(噪声过多)。相比之下,Pull Request能自动高亮显示变更内容。2017年7月27日更新:我们开发了snapshot-diff工具解决此问题。本文第三部分即将发布。
- 目前缺少类似Snapshots的比较工具。在我的示例中,需要手动对比
isSelected设为false与isSelected设为true时的快照差异。
快照的真正价值何在?
我想再次强调拉取请求环节的重要性。引用肯特·C·多兹的话:
.@spencercarnage 价值不在于此... 价值在于开发阶段的这个[1]和PR阶段的这个[2] pic.twitter.com/ULlRaD4EIZ
— Kent C. Dodds 💿🔴 (@kentcdodds) 2016年9月4日
我完全赞同:代码审查因快照而闪耀。但请注意,快照概念要发挥作用,你需要:
但必须强调:使用快照时,确保生成的文件得到正确审查至关重要。
— 克里斯托弗·中泽 (@cpojer) 2016年9月5日
这意味着:
必须确保PR中包含的__snapshots__文件夹被纳入代码审查
再补充一个价值:避免重复劳动。本文已(希望如此!)通过无需重写组件输出实现该理念。若想深入探索,请关注本系列后续文章。
若您的团队需要资深开发者协助,欢迎了解我们的React Native开发服务。我们期待与您探讨,共同将您的React Native项目提升至全新高度。