Web 前端单元测试到底要怎么写?

作者:deepfunc

来自:https://segmentfault.com/a/1190000015935519

随着 Web 应用的复杂程度越来越高,很多公司越来越重视前端单元测试。我们看到的大多数教程都会讲单元测试的重要性、一些有代表性的测试框架 api 怎么使用,但在实际项目中单元测试要怎么下手?测试用例应该包含哪些具体内容呢?

本文从一个真实的应用场景出发,从设计模式、代码结构来分析单元测试应该包含哪些内容,具体测试用例怎么写,希望看到的童鞋都能有所收获。

项目用到的技术框架

该项目采用 技术栈,用到的主要框架包括: 、 、 、 、 、 、 、 。

应用场景介绍

这个应用场景从 UI 层来讲主要由两个部分组成:

工具栏,包含刷新按钮、关键字搜索框

表格展示,采用分页的形式浏览

看到这里有的童鞋可能会说:切!这么简单的界面和业务逻辑,还是真实场景吗,还需要写神马单元测试吗?

别急,为了保证文章的阅读体验和长度适中,能讲清楚问题的简洁场景就是好场景不是吗?慢慢往下看。

设计模式与结构分析

在这个场景设计开发中,我们严格遵守 单向数据流 与 的最佳实践,并采用 来处理业务流, 来处理状态缓存,通过 来调用后台接口,与真实的项目没有差异。

分层设计与代码组织如下所示:

中间 中的内容都是 相关的,看名称应该都能知道意思了。

具体的代码请看这里:https://github.com/deepfunc/react-test-demo。

单元测试部分介绍

先讲一下用到了哪些测试框架和工具,主要内容包括:

,测试框架

,专测 react ui 层

,具有独立的 fakes、spies、stubs、mocks 功能库

,模拟 HTTP Server

如果有童鞋对上面这些使用和配置不熟的话,直接看官方文档吧,比任何教程都写的好。

接下来,我们就开始编写具体的测试用例代码了,下面会针对每个层面给出代码片段和解析。那么我们先从 开始吧。

为使文章尽量简短、清晰,下面的代码片段不是每个文件的完整内容,完整内容在这里:https://github.com/deepfunc/react-test-demo。

actions

业务里面我使用了 来产生 ,这里用工具栏做示例,先看一段业务代码:

对于 测试,我们主要是验证产生的 对象是否正确:

这个测试用例的逻辑很简单,首先构建一个我们期望的结果,然后调用业务代码,最后验证业务代码的运行结果与期望是否一致。这就是写测试用例的基本套路。

我们在写测试用例时尽量保持用例的单一职责,不要覆盖太多不同的业务范围。测试用例数量可以有很多个,但每个都不应该很复杂。

reducers

接着是 ,依然采用 的 来编写 ,这里用表格的来做示例:

这里的状态对象使用了 。

对于 ,我们主要测试两个方面:

对于未知的 ,是否能返回当前状态。

对于每个业务 type ,是否都返回了经过正确处理的状态。

下面是针对以上两点的测试代码:

这里的测试用例逻辑也很简单,依然是上面断言期望结果的套路。下面是 selectors 的部分。

selectors

的作用是获取对应业务的状态,这里使用了 来做缓存,防止 未改变的情况下重新计算,先看一下表格的 selector 代码:

这里的分页器部分参数在项目中是统一设置,所以 reselect 很好的完成了这个工作:如果业务状态不变,直接返回上次的缓存。分页器默认设置如下:

那么我们的测试也主要是两个方面:

对于业务 selector ,是否返回了正确的内容。

缓存功能是否正常。

测试代码如下:

测试用例依然很简单有木有?保持这个节奏就对了。下面来讲下稍微有点复杂的地方,sagas 部分。

sagas

这里我用了 处理业务流,这里具体也就是异步调用 api 请求数据,处理成功结果和错误结果等。

可能有的童鞋觉得搞这么复杂干嘛,异步请求用个 不就完事了吗?别急,耐心看完你就明白了。

这里有必要大概介绍下 的工作方式。saga 是一种 的生成器函数 - Generator ,我们利用他来产生各种声明式的 ,由 引擎来消化处理,推动业务进行。

这里我们来看看获取表格数据的业务代码:

不熟悉 的童鞋也不要太在意代码的具体写法,看注释应该能了解这个业务的具体步骤:

从对应的 里取到调用 api 时需要的参数部分(搜索关键字、分页),这里调用了刚才的 selector。

组合好参数并调用对应的 api 层。

如果正常返回结果,则发送成功 action 通知 reducer 更新状态。

如果错误返回,则发送错误 action 通知 reducer。

那么具体的测试用例应该怎么写呢?我们都知道这种业务代码涉及到了 api 或其他层的调用,如果要写单元测试必须做一些 mock 之类来防止真正调用 api 层,下面我们来看一下 怎么针对这个 saga 来写测试用例:

这个测试用例相比前面的复杂了一些,我们先来说下测试 saga 的原理。前面说过 saga 实际上是返回各种声明式的 ,然后由引擎来真正执行。所以我们测试的目的就是要看 的产生是否符合预期。那么 到底是个神马东西呢?其实就是字面量对象!

我们可以用在业务代码同样的方式来产生这些字面量对象,对于字面量对象的断言就非常简单了,并且没有直接调用 api 层,就用不着做 mock 咯!这个测试用例的步骤就是利用生成器函数一步步的产生下一个 ,然后断言比较。

从上面的注释 3、4 可以看到, 还提供了一些辅助函数来方便的处理分支断点。

这也是我选择 的原因:强大并且利于测试。

api 和 fetch 工具库

接下来就是api 层相关的了。前面讲过调用后台请求是用的 ,我封装了两个方法来简化调用和结果处理: 、 ,分别对应 GET 、POST 请求。先来看看 api 层代码:

业务代码很简单,那么测试用例也很简单:

由于 api 层直接调用了工具库,所以这里用 来替换工具库达到测试目的。

接着就是测试自己封装的 fetch 工具库了,这里 fetch 我是用的 ,所以选择了 来模拟 Server 进行测试,主要是测试正常访问返回结果和模拟服务器异常等,示例片段如下:

基本也没什么复杂的,主要注意 fetch 是 promise 返回, 的各种异步测试方案都能很好满足。

剩下的部分就是跟 UI 相关的了。

容器组件

容器组件的主要目的是传递 state 和 actions,看下工具栏的容器组件代码:

那么测试用例的目的也是检查这些,这里使用了 来模拟 redux 的 store :

很简单有木有,所以也没啥可说的了。

UI 组件

这里以表格组件作为示例,我们将直接来看测试用例是怎么写。一般来说 UI 组件我们主要测试以下几个方面:

是否渲染了正确的 DOM 结构

样式是否正确

业务逻辑触发是否正确

下面是测试用例代码:

得益于设计分层的合理性,我们很容易利用构造 来达到测试目的,结合 和 ,测试用例依然保持简单的节奏。

总结

以上就是这个场景完整的测试用例编写思路和示例代码,文中提及的思路方法也完全可以用在 、 项目上。完整的代码内容在 这里 (重要的事情多说几遍,各位童鞋觉得好帮忙去给个 :star: 哈)。

最后我们可以利用覆盖率来看下用例的覆盖程度是否足够(一般来说不用刻意追求 100%,根据实际情况来定):

单元测试是 TDD 测试驱动开发的基础。从以上整个过程可以看出,好的设计分层是很容易编写测试用例的,单元测试不单单只是为了保证代码质量:他会逼着你思考代码设计的合理性,拒绝面条代码 :muscle:

借用 Clean Code 的结束语:

2005 年,在参加于丹佛举行的敏捷大会时,Elisabeth Hedrickson 递给我一条类似 Lance Armstrong 热销的那种绿色腕带。这条腕带上面写着“沉迷测试”(Test Obsessed)的字样。我高兴地戴上,并自豪地一直系着。自从 1999 年从 Kent Beck 那儿学到 TDD 以来,我的确迷上了测试驱动开发。

不过跟着就发生了些奇事。我发现自己无法取下腕带。不仅是因为腕带很紧,而且那也是条精神上的紧箍咒。那腕带就是我职业道德的宣告,也是我承诺尽己所能写出最好代码的提示。取下它,仿佛就是违背了这些宣告和承诺似的。

所以它还在我的手腕上。在写代码时,我用余光瞟见它。它一直提醒我,我做了写出整洁代码的承诺。

●编号772,输入编号直达本文

●输入m获取文章目录

推荐↓↓↓

Web开发

更多推荐《18个技术类微信公众号》

涵盖:程序人生、算法与数据结构、黑客技术与网络安全、大数据技术、前端开发、Java、Python、Web开发、安卓开发、iOS开发、C/C++、.NET、Linux、数据库、运维等。

  • 发表于:
  • 原文链接https://kuaibao.qq.com/s/20180824B0IEO500?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券