粉丝见面会:
粉丝甲:“锵哥,时间好快呀,我们都已经进入第八期了”
锵哥:“是的,time fly 嘛,感谢老铁的支持哈”
粉丝甲:“锵哥,我发现其实并没有那么多人在看你的文章,为何你还会坚持写呢?”
锵哥:“阅读量确实很一般,但是有些事情需要长期的坚持,一个月,一年,甚至四五年的坚持,才会有不一样的结果,犹如健身一般”
粉丝甲:“果然是个狠人,加油!”
前言:
今天我们要聊一聊关于React的测试问题,相信很多人一脸懵逼,React到底是怎么测试呢,让我们来一起看看吧!
正文:
章节:《深入浅出React和Redux》(第八章:单元测试)
1.我发现写单元测试实际上提高了我的编程速度---Martin Fowler
2.本章会介绍
A:单元测试原则
B:React和Redux的单元测试环境
C:单元测试React组件的方法
D:单元测试Redux各个部分的方法
3.因为React和Redux基于函数式编程的思想,所以应用功能更容易拆分成容易测试的模块,对应产出代码的可测试性也更高
4.从不同的角度,可将测试划分为如下不同的种类
A:从人工操作还是写代码来操作的角度,可以分为手工测试和自动化测试
B:从是否需要考虑系统的内部设计角度,可以分为白盒测试和黑盒测试
C:从测试对象的级别,可以分为单元测试、集成测试和端到端测试
D:从测试验证的系统特性,又可以分为功能测试、性能测试和压力测试
5.我们只探讨特定于React和Redux的测试技巧,而体现特殊性的就是单元测试
6.单元测试是一种自动化测试,测试代码和被测的对象非常有关,比如测试React组件的代码就和测试jQuery插件的代码完全不是一回事
7.测试一个根据输入返回输出的纯函数,要比测试一个包含很多状态的对象容易得多。因为纯函数的结果根据输入完全可以预测,而为了测试一个对象在某个状态下的行为,还要首先让这个对象处于那个状态,测试代码自身就会变得很长、让开发者担心测试代码本身就可能因为复杂化而出问题。
8.要实现单元测试,需要单元测试测试环境,包括下面几个方面
A:单元测试框架
B:单元测试代码组织
C:辅助工具
9.构建React和Redux单元测试环境,首要要确定测试框架,有很多选择,最常见的有以下两种
A:用Mocha测试框架,但是Mocha并没有断言库,所以往往还要配合Chai断言库来使用,也就是Mocha+Chai的组合
B:使用React的本家Facebook出品的Jest,Jest自带了断言等功能,相当于包含了Mocha和Chai的功能,不过Jest的语法和Chai并不一致。
10.在create-react-app创建的应用中自带了Jest库,所以本书的代码库中单元测试都是基于Jest框架编写的代码,在任何一个create-react-app产生的应用代码目录下,用命令行执行下列代码,就会进入单元测试的界面:
npm run test
11.Jest会自动在当前目录下寻找满足下列任一条件的JavaScript文件作为单元测试代码来执行
A:文件名以.test.js为后缀的代码文件
B:存于_test_目录下的代码文件
12.单元测试代码的最小单元是测试用例(test case),每一个测试用例考验的是被测试对象在某一个特定场景下是否有正确的行为。在Jest框架下,每个测试用例用一个it函数代表,it函数的第一个参数是一个字符串,代表的就是测试用例名称,第二参数是一个函数,包含的就是实际的测试用例过程。
13.为了测试被测对象在多种情况下的行为,就需要创建多个单元测试用例,因此,接下来的问题就是如何组织多个it函数实例,也就是测试套件(test suite)的构建
14.describe函数包含与it函数一样的参数,两者主要的区别就是describe可以包好it或者另一个describe函数调用,但是it去不能
15.将多个it放到一个describe中的主要目的是为了重用共同的环境设置。比如一组it中都需要创建一个Redux Store实例作为测试的前提条件,让每个it中都进行这个操作就是重复代码,这是就应该把这些it放到一个describe中,然后利用describe下的beforeEach函数来执行共同的创建Redux Store工作。
16.describe中有如下特殊函数可以帮助重用代码
A:beforeAll,在开始测试套件开始之前执行一次
B:afterAll,在结束测试套件中所有测试用例之后执行一次
C:beforeEach,每个测试用例在执行之前都执行一次
D:afterEach,每个测试用例在执行之后都执行一次
17.假设一个describe中包含上面所描述的四个函数,并包含两个it测试用例,那么首先执行beforeAll函数,随后执行beforeEach函数,接下来执行第一个it函数,接着执行afterEach函数,接下来又是依次执行beforeEach、it和afterEach函数,最后执行的是afterAll函数。
18.要方便地测试React组件,就需要用到Enzyme,有意思的是Enzyme并不是Facebook出品,而是AirBnB贡献出来的开源项目。要使用Enzyme,需要安装对应的npm包:
npm install -save-dev enzyme react-addons-test-utils
19.上面的react-addons-test-utils是Facebook提供的单元测试辅助库,Enzyme依赖这个库,但是这个库的本身功能没有Enzyme那么强大
20.测试React组件,需要将React组件渲染出来看一看结果,不过Enzyme认为并不是所有的测试过程都需要把React组件的DOM树都渲染出来,尤其对于包含复杂子组件的React组件,如果深入渲染整个DOM树,那就要渲染所有子组件,可是子组件可能会有其他依赖关系,比如依赖于某个React Context值,为了渲染这样的子组件需要耗费很多精力准备测试环境,这种情况下啊,针对目标组件的测试只要让它渲染顶层组件就好了,不需要测试子组件
21.Enzyme支持三种渲染方法
A:shallow,只渲染顶层React组件,不渲染子组件,适合只测试React组件的渲染行为
B:mount,渲染完整的React组件包含子组件,借助模拟的浏览器环境完成事件处理功能
C:render,渲染完整的React组件,但是只产生HTML,并不进行事件处理
22.开源社区存在很多模拟网络请求的单元测试辅助工具,不过对于“模拟”这件事,不应该只是局限于网络请求,所以这里我们使用一个全能的模拟工具sinon.js。
23.sinon.js功能强大,可以改变指定对象的行为,甚至改变测试环境的时钟设置
npm install -save-dev sinon
24.虽然Redux简单易用,但是在某些情况下并不需要完整的Redux功能,一个模拟的Redux Store使用起来更加方便。比如对于测试一个异步action构造函数时,异步action构造函数会往Store中连续派发action对象,从测试角度并不需要action对象被派发到reducer中,只要能够检查action对象被派发就足够了,这样就能够用上redux-mock-store。安装redux-mock-store的方法如下
npm install -save-dev redux-mock-store
25.单元测试的要义是一次只测试系统的一个功能点。
26.单元测试的基本套路如下
A:预设参数
B:调用纯函数
C:用expect验证纯函数的返回结果
27.通过sinon提供的stub函数来“篡改”函数行为,stub第一个参数是一个对象,第二个参数是这个函数的字符串名,返回一个stub对象,通过这个stub上的对象可以指定被“篡改”函数的行为。通过stub函数实际上可以“篡改”任何一个函数的行为,对fetch这样的全局函数也不例外,因为全局函数相当于在global对象上的一个函数。
28.利用boforeEach中创造的stub对象stubbedFetch规定fetch函数被调用时返回一个指定的mockReposonse。这样,fetchWeather函数中的fetch函数行为就完成被操纵,毕竟我们并不需要测试fetch函数的行为,所以只需要让fetch函数返回我们想要的结果就行
29.在Jest中测试异步函数有两种方法,一种是代表测试用例的函数增加一个参数,习惯上这个参数叫做done,Jest发现这个参数存在就会认为这个参数是一个回调函数,只有这个回调函数被执行才算是测试用例结束。测试用例使用done参数的例子如下:
it('should timeout',(done)=>{
})
在上面的例子中,这个it测试用例最终会因为超时而失败,因为没有任何代码去调用done函数
30.除了使用done参数,还有另外一个办法,就是让测试用例函数返回一个Promise对象,这样也等于告诉Jest这个测试用例是一个异步过程,只有当返回的Promise对象完结的时候,这个测试用例才算结束
31.断言部分我们使用了redux-mock-store所创造Store的getActions函数,注意这个函数并不是redux的功能,但能够帮助我们读取到所有派发到Store上的actions,在单元测试中非常适用。
32.reducer是纯函数,所以测试非常简单,所要做的就是创造state和action对象,传递给reducer函数,验证结果即可
33.对于一个无状态的React组件,可以使用Enzyme的shallow方法来渲染,因为shallow方法只渲染一层,所以不会牵涉子组件的React组件渲染,将单元测试专注于被测试的React组件本身
34.习惯上,把Enzyme函数渲染的结果命名为wrapper,对wrapper可以使用contains函数判断是否包含某一个子组件。在这里,shallow并没有渲染产生子组件Link的DOM元素,所以完成可以用contains来判断是否包含Link组件。
35.这种单元测试不深入渲染React子组件,主要的意义是可以简化测试过程,因为React子组件的完全渲染可能引入其他的依赖关系
36.如果将应用状态存放在Redux Store上,配合使用react-redux库,所有有状态的React组件都是通过connect函数产生的组件,被称为“被连接的组件”
37.TodoList这样的一个组件依赖于一个Redux Store实例,而且能够实实在在地提供内容,所以不再使用redux-mock-store,而是使用一个货真价实的Redux Store,需要创造一个store。
38.为了将这个Store放在React Context,还需要创造Provider,使用Enzyme的mount方法渲染的是Provider包裹起来的TodoList组件
39.上面创造Provider的过程看起来有一点麻烦,主要是因为TodoList组件还包含了TodoItem组件也是连接到Store组件,如果被测试组件并不包含任何其他链接到Store的子组件,那就可以直接在组件渲染中用名为store的prop
观后感回放:
粉丝路人甲:“原来是这么测试的,涨见识了!”
锵哥:“对的,只有看的多,你才会有可能懂得多”
粉丝路人甲:“嗯,有道理!”
广告:
本人从事全栈工程师,目前主要工作能力涵盖的范围有:android,ios,h5,pcWeb,react,vue,node,java服务端,微信服务号,微信小程序,支付宝生活号,支付宝小程序。
本公众号会不定期的将自己的研发感悟,以及心得笔记无私奉献给大家。还等啥,赶快上车吧,铁子们!!!?(还会有其他的福利哦!快来吧)
官方订阅号:锵哥的觉悟
微信号:DY_suixincq
二维码:
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。