本文主要介绍前端单元测试的一些技术方案。
单元测试的技术方案很多,不同工具之间有互相协同,也存在功能重合,给我们搭配测试方案带来不小的困难,而且随着 , 的出现,单元测试又增加了很多其他步骤,完整配置起来往往需要很大的时间成本。我希望通过对这些工具的各自作用的掌握,了解完整的前端测试技术方案。前端单元测试的领域也很多,这里主要讲对于前端组件如何进行单元测试,最后会主要介绍下对于 组件的一些测试方法总结。
通用测试
单元测试最核心的部分就是做断言,比如传统语言中的 函数,如果当前程序的某种状态符合 的期望此程序才能正常执行,否则直接退出应用。所以我们可以直接用 中自带的 模块做断言。
用最简单的例子做个验证
这种例子能够满足基础场景的使用,也可以作为一种单元测试的方法。
自带的 模块提供了下面一些断言方法,只能满足一些简单场景的需要。
自带的 不是专门给单元测试使用, 提供的错误信息文档性不好,上面的 最终执行下来会产生下面的报告:
由于自带的模块依赖 自身的版本,没办法自由升级,所以使用内置的包灵活性有时候不太够,另外我们很多断言函数也需要在浏览器端执行,所以我们需要同时支持浏览器和 端的断言库。同时观察上面的输出可以发现,这个报告更像是程序的错误报告,而不是一个单元测试报告。而我们在做单元测时往往需要断言库能够提供良好的测试报告,这样才能一目了然地看到有哪些断言通过没通过,所以使用专业的单元测试断言库还是很有必要。
chai
chai
是目前很流行的断言库,相比于同类产品比较突出。 提供了 TDD[2] (Test-driven development)和 BDD[3] (Behavior-driven development) 两种风格的断言函数,这里不会过多介绍两种风格的优缺,本文主要以 风格做演示。
TDD 风格的 chai
比 自带的 增加了一个断言说明参数,可以通过这个参数提高测试报告的可读性
BDD 风格的 chai
的 风格使用 函数作为语义的起始,也是目前几乎所有 工具库都遵循的风格。
的 断言风格如下
的思想就是写单元测试就像写产品需求,而不关心内部逻辑,每一个用例阅读起来就像一篇文档。例如下面的用例:
可以看到这种风格的测试用例可读性更强。
其他的断言库还有 better-assert[4] , unexpected.js[5] 这些断言库都只提供纯粹的断言函数,可以根据喜好选择不同的库使用。
有了断言库之后我们还需要使用测试框架将我们的断言更好地组织起来。
mocha 和 Jasmine
mocha jasmine
是一个经典的测试框架(Test Framework),测试框架提供了一个单元测试的骨架,可以将不同子功能分成多个文件,也可以对一个子模块的不同子功能再进行不同的功能测试,从而生成一份结构型的测试报告。例如 就提供了 和 描述用例结构,提供了 , , , 生命周期函数,提供了 , , , 用以执行指定部分测试集。
测试框架不依赖底层的断言库,哪怕使用原生的 模块也可以进行。给每一个文件都要手动引入 比较麻烦 ,这时候可以给 配置全局脚本,在项目根目录 文件中加载断言库, 这样每个文件就可以直接使用 函数了。
使用 mocha 可以将我们的单元测试输出成一份良好的测试报告
当出现错误时输出如下
因为运行在不同环境中需要的包格式不同,所以需要我们针对不同环境做不同的包格式转换,为了了解在不同端跑单元测试需要做哪些事情,可以先来了解一下常见的包格式。
目前我们主流有三种模块格式,分别是 , , 。
AMD
AMD[6] 是 推广过程中流行的一个比较老的规范,目前无论浏览器还是 都没有默认支持。 的标准定义了 和 函数,用来定义模块及其依赖关系, 用以加载模块。例如
这里使用了 作为 引擎, 可以看到 函数会定义当前依赖了哪些模块并将模块加载完成后异步回调给当前模块,这种特性使得 AMD 尤为适合浏览器端异步加载。
我们可以使用 打包一份 模块看下真实代码
最终生成代码(精简了不相关的逻辑)
在浏览器/ 中想要使用 需要全局引入 ,对单元测试而言比较典型的问题是在初始化 时会询问是否使用 ,不过一般现在很少有人使用了。
CommonJS
可以缩写成 , 其 规范 [7]主要是为了定义 的包格式, 定义了三个关键字, 分别为 ,, , 目前几乎所有 包以及前端相关的包都会转换成该格式, 在浏览器端需要使用 或者 等工具打包后才能执行。
ES Module
是 中定义的一种模块规范,该规范定义了 代表为 和 ,是我们开发中常用的一种格式。虽然目前很多新版浏览器都支持 了,支持在浏览器中直接运行 代码,但是浏览器不支持 ,所以我们的原始 代码在浏览器上依然无法运行,所以这里我暂且认为浏览器不支持 代码, 依然需要做一次转换。
下表为每种格式的支持范围,括号内表示需要借助外部工具支持。
单元测试要在不同的环境下执行就要打不同环境对应的包,所以在搭建测试工具链时要确定自己运行在什么环境中,如果在 中只需要加一层 转换,如果是在真实浏览器中,则需要增加 处理步骤。
所以为了能够在 环境的 中使用 有两种方式
第一种方式略过,第二种方式使用下面的配置
同样地如果在项目中用到了 , 就可以使用 来解决,因为 本身支持 转换成 , 所以支持了 后就不需要使用 来转换了。(这里假设使用了 的默认配置)
自身支持浏览器和 端测试,为了在浏览器端测试我们需要写一个 html, 里面使用 的文件,然后再将本地所有文件插入到html中才能完成测试,手动做工程化效率比较低,所以需要借助工具来实现这个任务,这个工具就是 。
本质上就是在本地启动一个web服务器,然后再启动一个外部浏览器加载一个引导脚本,这个脚本将我们所有的源文件和测试文件加载到浏览器中,最终就会在浏览器端执行我们的测试用例代码。所以使用 + + 即可搭建一个完整的浏览器端的单元测试工具链。
这里 初始化时选择了 的支持,然后第二个 一般为否,除非业务代码中使用了类型的包。第三个选用 作为测试浏览器。然后再在代码里单独配置下 。
的 作用是在全局注入一些依赖,这里的配置就是将 和 提供的测试相关工具暴露在全局上供代码里使用。 只是将我们的文件发送到浏览器去执行,但是根据前文所述我们的代码需要经过 或 打包后才能运行在浏览器端。
如果原始代码已经是 了,可以使用 来支持浏览器端运行,基本零配置,但是往往现实世界比较复杂,我们有 , 以及 要处理,所以这里我们使用 。
下面是 的配置信息。
这里我们测试一个 程序代码如下
并且打开了本地浏览器
karma browser
可以看到现在已经在真实浏览器中运行测试程序了。
因为图形化的测试对 机器不友好,所以可以选择 代替 。
再者这些都是很重的包,如果对真实浏览器依赖性不强,可以使用 在 端模拟一个浏览器环境。
稍微总结下工具链
一个测试流水线往往需要很多个工具搭配使用,配置起来比较繁琐,还有一些额外的工具例如单元覆盖率(istanbul),函数/时间模拟 (sinon.js)等工具。工具之间的配合有时候不一定能够完美契合,选型费时费力。
的出现就稍微缓解了一下这个问题,但也不够完整,提供一个测试框架,里面包含了 测试流程框架,断言函数,mock工具等测试中会遇到的工具。可以近似地看作 。
接下来试一试 的工作流程。
使用 初始化之后会在当前目录中生成目录, 其中包含一份默认的配置文件
如果希望加载一些全局的配置可以在 目录中放一些文件, 正如配置所言,jasmine 在启动时会去执行 目录下的所有文件。
比如我们常常使用 语法,就需要增加的支持。
新增 写入如下配置即可。
和 一样,如果需要 的支持,可以使用如下配置
配置文件中的 是 约定的用例文件目录,规定了用例文件格式为 。
有了这份默认配置就可以按照要求写用例,例如
的断言风格和 很不一样, 的 如下,与 相比少写了很多 ,而且支持的功能更加清晰,不用考虑如何组合使用的问题,而且下文介绍的 测试框架也是使用这种风格。
运行 即可生成测试报告
默认的测试报告不是很直观, 如果希望提供类似 风格的报告可以安装 ,在 目录中添加一个配置文件, 例如。
此时输出的用例报告如下
jasmine
如果在 中执行 DOM 级别的测试,就依然需要借助 或 了,具体的配置这里就不再赘述。
总结下 的工具链
JEST
jest
是 出的一个完整的单元测试技术方案,集 测试框架, 断言库, 启动器, 快照,沙箱,mock工具于一身,也是 官方使用的测试工具。 和 具有非常相似的 ,所以在 中用到的工具在 中依然可以很自然地使用。可以近似看作 。
虽然 Jest 提供了很丰富的功能,但是并没有内置 支持,所以依然需要根据不同运行时对代码进行转换,由于 Jest 主要运行在 中,所以需要使用 将 转换成 。
Jest 的默认配置
在 或 下增加 代码的支持
使用 生成测试报告
jest
对于 和 支持也可以通过修改 的配置解决
Jest 在真实浏览器环境下测试
目前 不支持直接在真实浏览器中进行测试,其默认的启动器只提供了一个 环境,在浏览器中进行单元测试目前只有 方案能做到,所以也可以使用 + 方案实现,但是不建议这么做,因为 自身太重,使用 + 能达到基本一样的效果。
另外还有一个比较流行的 方案 + , 由于 不属于单元测试范畴,这里不再展开。
工具链总结
稍作总结
上面的内容介绍了 , , , 和 , 每种工具分别对应一些自己特有的工具链,在选取合适的测试工具时根据实际需要选择, 测试领域还有非常多的工具数都数不过来,下面来看下 React 单元测试的一些方法。
使用 Jest + Enzyme 对 React 进行单元测试
enzyme
基础配置如下:
这个包是为了解决一些使用 未实现行为触发警告的问题。
上面建立了一个使用 比较友好的环境,可以直接在全局作用域里引用 , , 等 。此外 还注册了许多友好的断言函数到 中,如下所示,参考地址[8]
提供了三种渲染组件方法
Shallow 渲染
因为 模式仅能感知到第一层自定义子组件组件,往往只能用于简单组件测试。例如下面的组件
渲染虽然不是真正的渲染,但是其组件生命周期会完整地走一遍。
使用 能感知到的结构如下, 注意看到 作为 组件的 能够被检测到,但是 的内部结构无法感知。
shallow
支持的选择器支持我们熟悉的 语法,这种情况下我们可以对 结构做如下测试
如果我们想去测试对应组件的 / 也可以很方便测试,不过目前存在缺陷, 能通过 , 直接测试, 但是 组件无法测试 。
虽然会返回同样的一个 对象,但是这个对象的子结构是未展开的,如果想测试 内部结构,需要再 一次。
也可以改变组件的 , 触发组件重绘
另一个常见的场景是事件模拟,事件比较接近真实测试场景,这种场景下使用 存在诸多缺陷,因为 场景事件不会像真实事件一样有捕获和冒泡流程,所以此时只能简单的触发对应的 达到测试目的。
关于这些网上有人总结了 模式下的一些不足
其实上面几点说明了一个现象是 往往只适合一种理想的场景,一些依赖浏览器行为表现的操作 无法满足,这些和真实环境相关的就只能使用了。
Mount 渲染
渲染的对象结构为 其提供了和 几乎一样的 , 差异很小。
在 层面的一些差异如下
另外由于 使用 进行渲染,所以其更加接近真实场景,在这种模式下我们能观察到整个 结构和React组件节点结构。
mount
在 中无法模拟的事件触发问题在 下就不再是问题。
总结一下 中能做的 都能做,中能做的 不一定能做。
Render 渲染
内部使用 渲染成字符串,再经过 转换成内存中的结构,返回 实例,能够完整地渲染整个 树,但是会将内部实例的状态丢失,所以也称为 。这种渲染能够进行的操作比较少,这里也不作具体介绍,可以参考 官方文档[9] 。
总结
如果让我推荐的话,对于真实浏览器我会推荐 + 方案测试,对于 测试 + 在 环境下已经能覆盖大部分场景。另外测试 组件除了 提供的操作, 中还有很多其他有用的特性,比如可以 一个 组件的实现,调整 时钟等,真正进行单元测试时,这些工具也是必不可少的,整个单元测试技术体系包含了很多东西,本文无法面面俱到,只介绍了一些距离我们最近的相关的技术体系。
参考
本文发布自 网易云音乐大前端团队[10],文章未经授权禁止任何形式的转载。我们常年招收前端、iOS、Android,如果你准备换工作,又恰好喜欢云音乐,那就加入我们 grp.music-fe(at)corp.netease.com!
参考资料
[1]
江水: https://www.zhihu.com/people/li-quan-wei-41
[2]
TDD: https://en.wikipedia.org/wiki/Test-driven_development
[3]
BDD: https://en.wikipedia.org/wiki/Behavior-driven_development
[4]
better-assert: https://github.com/tj/better-assert
[5]
unexpected.js: https://unexpected.js.org/
[6]
AMD: https://github.com/amdjs/amdjs-api/blob/master/AMD.md
[7]
规范 : http://wiki.commonjs.org/wiki/Modules/1.1
[8]
参考地址: https://github.com/enzymejs/enzyme-matchers/blob/master/packages/jest-enzyme/README.md
[9]
官方文档: https://enzymejs.github.io/enzyme/docs/api/render.html
[10]
网易云音乐大前端团队: https://github.com/x-orpheus
结语
到此这篇单元测试方案(单元测试步骤及测试内容)的文章就介绍到这了,更多相关内容请继续浏览下面的相关推荐文章,希望大家都能在编程的领域有一番成就!版权声明:
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权、违法违规、事实不符,请将相关资料发送至xkadmin@xkablog.com进行投诉反馈,一经查实,立即处理!
转载请注明出处,原文链接:https://www.xkablog.com/te-unit/67246.html