返回介绍

四、单元测试

发布于 2024-03-27 21:02:24 字数 4435 浏览 0 评论 0 收藏 0

Jest

在 JavaScript 的世界里,单元测试的框架很多,品牌最老名气最响的是 Mocha ,不过,不要纠结于名气,请使用 Jest 。你不会后悔的,接下来我告诉你为什么。

我们先假设,作为开发者,你是在团队中工作。所谓团队,就是有很多人一起工作,而且随着业务和团队的发展,人会越来越多,潜台词就是——不确定因素越来越多。

人和人之间交流会出现偏差,人的水平有高低之分,人也会犯错,总之,你不能指望所有人都把事情做得尽善尽美。

具体到单元测试这件事上来,测试驱动是开发喊了这么多年,为什么真正做到这一点的团队依然不多呢?因为,当团队变大之后,很多问题也就出现了

1、单元测试用例庞大,执行时间过长。

想象一下,一个代码库里假设有一千个单元测试用例,即使每个单元测试用例平均只需要 10 毫秒,那总时间也就需要 10 秒钟。好,假设代码库进一步扩大,有了一万个单元测试用例,那就跑一遍就需要 100 秒,已经超过了一分钟。这还只是保守估计,实际上单元测试用例的运行时间只会比这长。开发者如果每次修改都需要等待这么漫长的单元测试运行时间,肯定会三心二意上网去看其他东西。

2、 单元测试用例之间相互影响。

你可能也有这样的体验,代码库中的单元测试突然失败了,但是你修改的代码根本不会取影响失败的那个单元测试用例,怎么回事?这往往是因为某个成员以前的代码写得不好,影响了一个全局变量。当然,谁都知道单元测试应该在 setup 时创建环境,在 teardown 时恢复环境,可是,总会有人有马虎大意的情况,这时候你怎么办?要么你只好去修复一个本不是你改坏的代码,要么你干脆删掉那段不可靠的单元测试代码,不管怎样,这都会打击你支持测试驱动开发的决心。

Jest 较好地解决了上面说的问题,因为 Jest 最重要的一个特性,就是支持并行执行

Mocha 之类老牌单元测试框架,把所有的单元测试都放在一个环境中执行,这就使所有单元测试访问的是同样一个全局变量空间,所以只要测试代码没写好,就会互相影响。而且,为了保证执行正常,所有的单元测试必须一个接一个地执行,这是体系架构决定的,没有办法。

Jest 不同,Jest 为每一个单元测试文件创造一个独立的运行环境,换句话说,Jest 会启动一个进程执行一个单元测试文件,运行结束之后,就把这个执行进程废弃了,这个单元测试文件即使写得比较差,把全局变量污染得一团糟,也不会影响其他单元测试文件,因为其他单元测试文件是用另一个进程来执行

更妙的是,因为每个单元测试文件之间再无纠葛,Jest 可以启动多个进程同时运行不同的文件,这样就充分利用了电脑的多 CPU 多核,单进程 100 秒才完成的测试执行过程,8 核只需要 12.5 秒,速度快了很多。

Jest 还有很多其他友好的特性,大家可以自己去发掘,这里废话不多说,只想安利各位,测试 React 或者 JavaScript 代码,用 Jest!

使用 create-react-app 产生的项目自带 Jest 作为测试框架,不奇怪,因为 Jest 和 React 一样都是出自 Facebook。

运行下面的命令,就可以进入交互式的测试驱动开发模式:

npm test

Enzyme

虽然最好的 React 测试框架出自 Facebook 家,最受欢迎的 React 测试工具库却出自 Airbnb,这个工具库叫做 Enzyme。Enzyme 这个单词的含义是,至于命名原因已经无法考据,可能寓意着快速分解。

不过因为 Enzyme 不是 Facebook 家出品,所以使用 Enzyme 还真稍微有些麻烦——在 create-react-app 产生的应用中并不包含 Enzyme,需要我们自己来添加。

在项目目录下,通过下面的命令来安装 enzyme

npm i --save-dev enzyme enzyme-adapter-react-16

可以注意到,我们不光要安装 enzyme,还要安装 enzyme-adapter-react-16,这个库是用来作为适配器的。因为不同 React 版本有各自特点,所用的适配器也会不同,我们的项目中使用的是 16.4 之后的版本,所以用 enzyme-adapter-react-16;如果用 16.3 版本,需要用 enzyme-adapter-react-16.3;如果用 16.2 版本,需要用 enzyme-adapter-react-16.2;如果用更老的版本 15.5,需要用 enzyme-adapter-react-15。具体各个 React 版本对应什么样的 Adapter,请参考 enzyme 官方文档。

现在,可以在测试代码中使用 enzyme 了。我们以之前秒表应用中的 ControlButtons 组件为例,来说明如何做单元测试。

我们创造一个 ControlButtons.test.js,来容纳对应的测试用例,因为所有后缀为 .test.js 的文件都会被 Jest 认作是测试用例文件。

在代码中,需要使用 Adapter,代码如下

import {configure} from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';
configure({adapter: new Adapter()});

我们对 ControlButtons 组件的测试,就是要渲染它一次,看看渲染结果如何,enzyme 就能帮助我们做这件事。

比如,我们想要保证渲染出来的内容必须包含两个按钮,其中一个按钮的 class 名是 left-btn,另一个是 right-btn,那么我们就需要下面的单元测试用例:

import {shallow} from 'enzyme';

it('renders without crashing', () => {
  const wrapper = shallow(<ControlButtons />);
  expect(wrapper.find('.left-btn')).toHaveLength(1);
  expect(wrapper.find('.right-btn')).toHaveLength(1);
});

在这里我们使用了 shallow,其实也可以使用 mount。

shallow 和 mount 的区别,就是 shallow 只会渲染被测试的 React 组件这一层,不会渲染子组件;而 mount 则是完整地渲染 React 组件包括其所有子组件,包括触发 componentDidMount 生命周期函数。

原则上,能用 shallow 就尽量用 shallow,首先是为了测试性能考虑,其次是可以减少组件之间的影响,比如,一个组件 Foo 有子组件 Bar,如下

const Foo = () => ()
  <div>
     {/* other logic */
     <Bar />
  </div>
)

如果用 mount 去渲染 Foo,会连带 Bar 一起完全渲染,如果 Bar 出了什么毛病,那 Foo 的单元测试也过不了;如果用 shallow,只知道 Bar 曾经被用,即使 Bar 哪里出了问题,也不影响 Foo 的单元测试。

这并不是说我们就不管 Bar,Bar 的质量会由它自己的单元测试来检验,这就引出下一个话题——代码覆盖率。

代码覆盖率

你不能给自己的程序随便写几个单元测试,就说自己的代码已经测试好了,就像上面我只给 ControlButtons 组件写了一个测试用例,我并不能说整个秒表应用已经通过了测试。

你的代码测试覆盖率只有达到一定程度,才好说自己的代码已经被测试了。

剩下来就是一个纠结的问题:代码测试的覆盖率应该达到多少才算够?

以我个人的经验,代码覆盖率必须达到 100%,也就是说,一个应用不光所有的单元测试都要通过,而且所有单元测试都必须覆盖到代码 100% 的角落。

如果对覆盖率的要求低于 100%,时间一长,质量必定会越来越下滑。

遇到一个不好测试的代码,开发者倾向于不去考虑如何重构代码提高可测试性,而是直接忽略这部分代码不去测试,反正不要求 100% 嘛;遇到工期比较紧的时候,甚至会进一步降低代码覆盖率要求,用牺牲质量来加快开发速度,反正不要求 100% 嘛。

所以,如果你真的对代码质量认真负责的话,请坚守 100% 代码覆盖率的底线!

在 create-react-app 创造的应用中,已经自带了代码覆盖率的支持,运行下面的命令,不光会运行所有单元测试,也会得到覆盖率汇报

npm test -- --coverage

代码覆盖率包含四个方面:

  • 语句覆盖率
  • 逻辑分支覆盖率
  • 函数覆盖率
  • 代码行覆盖率

只有四个方面都是 100%,才算真的 100%。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文