前端自动化测试,现在已经成了不可缺少的技能。每个公司都在使用。也许你正欠缺这方面的知识,那这篇文章就可以帮你走入前端自动化测试的大门。这只是基础文章,希望能帮你入门就好。
很高兴你能和我一起学习这门新的课程,课程完全免费(文字+视频)。首先要说的是,虽然我以前在公司也写测试代码,也作前端自动化测试,但是并没有系统的总结和学习过,所以利用这篇文章,我将从头到尾详细系统的学习前端自动化测试。所以这个文章会站在一个学习者的角度,讲解前端自动化测试的方方面面。因为我并没有站在一个专家的角度来讲这些内容,所以好处就是你学起来不会有什么大的障碍无法逾越,而是非常顺滑的从简单到实战的过度。
为了尊重版权,在这里我会列出我参照的书籍和课程,你们也可以同步购买这些资料来进行学习。
虽然参照了很多资料,但是里边也加入了自己的很多理解和对知识的独立解读,如果你转载,请也写明出处(JSPang.com)。
这篇文章可能有几万字,长城不是一天建成的,文章和视频的更新频率大概是每周2-3节。这也是技术胖最快的速度了,因为技术胖目前还是一个全职的一线开发人员,每天都要在单位编写大量代码,所有文章和视频都需要下班后回家录制并发布,每天晚上会有两个小时左右编写文章。(如果有封闭式开发,也可能会断更,但是技术胖会尽快的把这个文章编写完成)
随着前端的发展,前端设计的领域已经越来越多,也越来越复杂。这就对我们前端工程化能力,提出了更高的要求。 好的前端工程化一般包括三个大的方面:
我知道一些公司,到现在还是没有前端自动化测试,甚至BOSS会说前端自动化测试会拉低工作效率,认为用处不大。这是完全错误的想法,你可以看到Github上任何大型的前端项目都有自动化测试代码。
其实我个人认为前端自动化测试是每个项目必备的一个环境,重要性我在这里就不作过多的介绍了。我的课程一直是重实践的。
npm
,不会服务端代码到无所谓。视频录制不宜,如果能支持一下技术胖,加入小密圈
,我将非常感谢。
这节课正式开始学习Jest的基础知识,这时候很多用过自动化测试的小伙伴肯定会问一个问题,这个问题就是为什么非要选择Jest这个自动化测试框架那,现在有好几种优秀的测试框架。所以我觉的有必要单独拿出一节课讲讲Jest的优点。
Jasmine : JavaScript测试框架(BDD-集成测试开发框架),这个也算是比较早的测试框架,我的第一个测试框架接触的就是Jasmine
. 好用,但我喜新厌旧。
MOCHA: 它是一个功能丰富的JavaScript测试框架,运行在Node.js
和浏览器中,使异步测试变得简单有趣。也是非常优秀的框架,但是我们公司没有用,所以我也什么发言权。
Jest:目前最流行的前端测试框架,几乎国内所有的大型互联网公司都在使用。具体好处我会在下面详细说清楚。
其实这三个框架用起来都差不多,如果你能学会一个,自己再稍微学习一下,就可以迅速掌握其它两个。
比较新:喜新厌旧是人的天性,新技术出来后你总想动手尝试一下,这个就和家花没有野花香是一个道理。作为一个程序员,你更要有拥抱全新知识的态度。绝不能固步自封,顽固不化。(你们这些小年青永远体会不了老男人去洗浴中心的喜悦和急切心情的)
基础很好:框架基础好就是性能好、功能多、简单易用,Jest在这三个方面你可以完全放心。绝对是一把好手,干就完事了。
速度快: 单独模块测试功能,比如说有两个模块A和B,以前都测试过了,这时候你只改动A模块,才次测试,模块B不会再跑一次,而是直接测试A模块。这就好比你去‘大宝剑’,你所有技师都试过了一次,下次你再来,直接就找最好的就行了。不用再测试一遍。(安心、放心)
API简单 :等你基础知识学完后,你就会发现API非常简单,数量也少。
隔离性好:Jest里会有很多的测试文件等待我们使用,Jest的执行环境都是隔离,这样就避免不同的测试文件执行的时候互相影响而造成出错。
IDE整合:Jest直接可以和很多编辑器(VSCode)进行融合,让测试变的更加简单。
多项目并行:比如我们写了Node.js的后台项目,用React写了一个前台项目,Jest是支持他们并行运行,让我们的效率更加提高了。
快出覆盖率:(测试代码覆盖率) 对于一个项目的测试都要出覆盖率的,Jest就可以快速出这样的覆盖率统计结果,非常好用。
我在课程中使用的编辑器是VSCode,这也是我认为比较好的一个编辑器。如果你使用的是其它编辑器,我建议你在学习期间可以先更换为VSCode
编辑器,因为课程中一些操作时VSCode独有的。
在E盘
新建一个文件夹,叫做JestTest
,我这里就直接用命令行建立了。
cd E:
mkdir JestTest
文件夹建立好后,打开VSCode,把问价夹拖入到编辑器中。
然后我们需要Node
的开发环境,所以要安装Node.js
这个软件,这个软件的网址如下.
安装过程跟QQ的安装过程基本一样,这里就不作过多介绍了。
安装好以后,你可以到VSCode中,输入node -v
来检查Node是否已经安装好了。如果安装好了,就会出现Node的版本号。这个版本号你可能和我的不太一样。
这里我们还可以使用npm -v
来检测npm这个工具是否也安装完成了
想要安装jest
必须要用package.json
文件来管理这个包,所以我们要使用初始化命令,来初始化。
使用npm init
命令来快速生成一个package.json
文件,有了这个文件就可以下载安装Jest前端测试框架了。
npm install jest@24.8.0 -D
-D
就保存到dev里边了,也就是说上线的时候我们不使用这个包,只有在开发的时候才进行测试。这样就安装完了Jest
框架。
这节课的内容就先到这里了,下节课我们开始写一个简单的测试用力和文件,让我们第一次解除前端测试。
我们已经安装好了基本Jest测试环境,现在就可以小试伸手,这节课我们就来写第一个单元测试的例子。
在项目根目录,新建两个文件,一个文件是dabaojian.js
(被测试文件),另一个是dabaojian.test.js
(测试文件)文件。
dabaojian.js
文件比如是我们写的一些业务逻辑方法,我们就那他当一个例子,最后要测试的就是这个文件。这里我们模仿一次去按摩的经历。
function baojian1(money){
return money>=200? '至尊享受':'基本按摩'
}
function baojian2(money){
return money>=1000? '双人服务':'单人服务'
}
module.exports = {
baojian1,baojian2
}
这个文件就是用来测试dabaojian.js
文件的,Jest会自动找对应的test
作为测试文件,所以我们这里也使用了.test
文件名。
先来看两个必须会的方法:
test方法:Jest封装的测试方法,一般填写两个参数,描述和测试方法
expect方法 :预期方法,就是你调用了什么方法,传递了什么参数,得到的预期是什么(代码中详细讲解)。
当然我们在编写测试代码前,用require
引入要测试的文件。
const dabaojian = require('./dabaojian.js')
const { baojian1 , baojian2 } = dabaojian
引入之后,就可以用test
和expect
方法进行测试了。具体代码如下:
const dabaojian = require('./dabaojian.js')
const { baojian1 , baojian2 } = dabaojian
test('保健1 300元',()=>{
expect(baojian1(300)).toBe('至尊享受')
})
test('保健2 2000元',()=>{
expect(baojian2(2000)).toBe('双人服务')
})
要进行测试,我们可以打开package.json
文件,然后把里边的scripts
标签的值修改为jest
.
{
"name": "jesttest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.8.0"
}
}
然后VSCode的终端窗口中输入yarn test
或者npm run test
,就可以进行测试了,具体的测试输出结果我会在视频中进行讲解。
好了,这样我们就可以完成最简单的单元测试了。希望小伙伴们从这节课,开始跟着技术胖敲代码,我还是那句话,如果你不动手,只听是永远学不会的。
上节课我们已经作了一个最简单的实例,你可以简单回顾一下,其实我们没有对Jest
进行任何的配置,直接使用就可以进行最简单的基本测试,这是因为Jest
是有一些默认配置的,这些默认配置就可以帮助我们完成前端自动化测试。但实际工作中不可能一直使用默认配置,会根据实际需求进行一些配置修改的。这节课我们就学一些简单的Jest
配置项。
在学习之前我们先来回答小伙伴问的一个问题,就是什么是“单元测试”?什么是“集成测试”?
单元测试:英文是(unit testing) 单,是指对软件中的最小可测试单元进行检查和验证。前端所说的单元测试就是对一个模块进行测试。也就是说前端测试的时候,你测试的东西一定是一个模块。
集成测试:也叫组装测试或者联合测试。在单元测试的基础上,将所有模块按照涉及要求组装成为子系统或系统,进行集成测试。
随着前端的发展,现在无论我们些React还是写Vue,其实代码已经全部都模块化了,所以使用Jest测试不需要额外加入任何的操作了。
问题回答完了,我们开始正式内容,我们可以直接使用npx
命令来进行初始化
npx jest --init
之后会有一些选项,你根据自己的需要进行选择就可以了:
Choose the test environment that will be used for testing ? 代码是运行在Node环境还是Web环境下?
Do you want Jest to add coverage reports ? 是否生成测试覆盖率文件?
Automatically clear mock calls and instrances between every test?是否需要在测试之后清楚模拟调用的一些东西?
在这三个选项选择之后,你会发现你的工程根目录下多了一个jest.config.js
的文件。打开文件你可以看到里边有很多Jest
的配置项。
先来了解一个概念code coverage
,代码测试覆盖率,就是我们的测试代码,对功能性代码和业务逻辑代码作了百分多少的测试,这个百分比,就叫做代码测试覆盖率。
coverageDirectroy
的配置是用来打开代码覆盖率的,如果我们把代码写成下面的样子,就说明打开了代码覆盖率的这个选项。
coverageDirectory : "coverage" //打开测试覆盖率选项
当这个选项被打开后,我们就可以使用下面的命令,jest
就会自动给我们生成一个代码测试覆盖率的说明。
npx jest --coverage
当然这个不仅会有一个简单的终端图表,还会生成一个coverage
的文件夹,这里边有很多文件。
我们可以打开coverage-lcov-reporrt-index.html
文件,这时候就可以看到一个网页形式的,非常漂亮的测试覆盖率报告。
那这个配置项可以作什么配置那?我们先来作一个修改,把coverageDirectory : "coverage"
改为coverageDirectory : "jspang"
,然后再使用npx jest --coverage
,这时候你会发现,生成的报告文件夹,不再是coverage
而是变成了jspang
。
** 有的小伙伴这时候可能对npx,这个命令不太熟悉,它到底是什么命令那?**
其实我作下面这样一个才做,你就能立刻明白npx
的作用了,我们可以打开package.json
文件,然后在里边作这样的修改。
{
"name": "jesttest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest",
"coverage":"jest --coverage"
},
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.8.0"
}
}
这时候你就可以使用yarn coverage
来进行生成代码测试覆盖率的文件了。
这节课我们学习的内容并不多,主要学了如何快速生成jest.config.js
配置文件和coverageDirecotry
配置项的详细介绍,这里边有很多配置文件,我们以后的课程中会慢慢讲解,我们不能一口车个胖子哦。所以这节课的内容就到这里,下节课我们继续学习。
这节我们讲一下Jest中的匹配器,其实匹配器我们在第3节作简单案例的时候已经使用了toBe()
,只不过我们一嘴带过,没有详细讲解。
在开始课程前,我们在项目跟目录下新建一个matchers.test.js
文件,这个文件用来专门讲解匹配器。然后在文件中写入下面的代码。
test('测试007号技师的匹配',()=>{
expect('007号技师').toBe('007号技师')
})
这段代码没有什么实际意义,我们只是为了讲解匹配器。写完代码后可以使用yarn test
运行一样,可以看到测试用例顺利通过。
toBe()
匹配器,是在工作中最常用的一种匹配器,简单的理解它就是相等。这个相当是等同于===
的,也就是我们常说的严格相等。
为了更好的理解toBe()
匹配器,我们新写一段代码,代码如下。
test('测试严格相等',()=>{
const a = {number:'007'}
expect(a).toBe({number:'007'})
})
写完后使用yarn test
来进行测试。我们可以知道,这个不是完全相等的,引用地址不同,所以测试应该是FAIL
。结果跟我们想象是一直的,通过这小段代码,你可以很清楚的知道toBe()
匹配器的作用了吧。
那你说我想让上面的测试结果是正确的,这时候我需要使用什么匹配器那?那就是使用toEqual()
匹配器。可以把它理解成只要内容相等,就可以通过测试,比如修改代码如下:
test('测试严格相等',()=>{
const a = {number:'007'}
expect(a).toEqual({number:'007'})
})
所以说当你不严格匹配但要求值相等时时就可以使用toEqual()
匹配器。
toBeNul()
匹配器只匹配null
值,需要注意的是不匹配undefined
的值。我们复制上面的代码,然后把代码修改成如下形式:
test('toBeNull测试',()=>{
const a = null
expect(a).toBeNull()
})
但是如果我们把a
的值改为undefined
,测试用例就通过不了啦。
那我们要匹配undefined
时,就可以使用toBeUndifined()
匹配器,比如写成如下代码。
test('toBeUndefined测试',()=>{
const a = undefined
expect(a).toBeUndefined()
})
如果我们把undefined
改为空字符串也是没有办法通过测试的。
toBeDefined()
匹配器的意思是只要定义过了,都可以匹配成功,例如下面的代码:
test('toBeDefined测试',()=>{
const a = 'jspang'
expect(a).toBeDefined()
})
这里需要注意的是,我给一个null
值也是可以通过测试的。
这个是true
和false
匹配器,就相当于判断真假的。比如说写下面这样一段代码:
test('toBeTruthy 测试',()=>{
const a = 0
expect(a).toBeTruthy()
})
这样测试就过不去了,因为这里的0,如果判断真假时,就是false,所以无法通过。同样道理null
也是无法通过的。
但是我们给个1
或者jspang
字符串,就可以通过测试了。
有真就有假,跟toBeTruthy()
对应的就是toBeFalsy
,这个匹配器只要是返回的false
就可以通过测试。
test('toBeFalsy 测试',()=>{
const a = 0
expect(a).toBeFalsy()
})
这节课我们就先讲这么多,其实Jest的匹配器还有很多,下节课我们继续讲解。讲多了我怕小伙伴们记不住。
这节我们继续讲解Jest中匹配器,注意我们讲的并不是全部,因为Jest中的匹配器实在是太多了,所以在这里我们也只讲重点。上节课其实讲的都是跟真假有关的匹配器,这节主要讲跟数字相关的匹配器。
每次修改测试用例,我们都手动输入yarn test
,这显得很lower。可以通过配置package.json
文件来设置。修改如下:
{
"name": "jesttest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest --watchAll",
"coverage":"jest --coverage"
},
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.8.0"
}
}
修改保存后,我们在终端再次运行yarn test
,这时候测试一次后,它并没有结束,而是等待测试文件的变化,变化后就会自动进行测试。
这个是用来作数字比较的,大于什么数值,只要大于传入的数值,就可以通过测试。我们来写一段代码来看一下。
test('toBeGreaterThan匹配器',()=>{
const count = 10
expect(count).toBeGreaterThan(9)
})
toBeLessThan
跟toBeGreaterThan
相对应的,就是少于一个数字时,就可以通过测试。代码如下:
test('toBeLessThan匹配器',()=>{
const count = 10
expect(count).toBeLessThan(11)
})
10比11小,所以测试用例顺利通过。
当测试结果数据大于等于期待数字时,可以通过测试。比如下面的代码是没办法通过测试的。
test('toBeGreaterThan匹配器',()=>{
const count = 10
expect(count).toBeGreaterThan(10)
})
但是我们使用了toBeGreaterThanOrEqual()
就可以通过测试。
test('toBeGreaterThanOrEqual匹配器',()=>{
const count = 10
expect(count).toBeGreaterThanOrEqual(10)
})
这个跟toBeGreaterThanOrEqual()
相对应,所以就不做过多的介绍了。
这个是可以自动消除JavaScript
浮点精度错误的匹配器,举个例子,比如我们让0.1
和0.2
相加,这时候js
得到的值应该是0.30000000000004
,所以如果用toEqual()
匹配器,测试用例会通过不了测试的。
比如我们把测试用例写成这样,就不会通过:
test('toEqual匹配器',()=>{
const one = 0.1
const two = 0.2
expect(one + two).toEqual(0.3)
})
这时候我们就需要使用toBeCloseTo()
匹配器,可以顺利通过测试,代码如下:
test('toBeCloseTo匹配器',()=>{
const one = 0.1
const two = 0.2
expect(one + two).toBeCloseTo(0.3)
})
这样就可以顺利通过测试了,这就是它的作用。
这节讲的匹配器都是跟数字有关的匹配器,可以动手做一下,但是我个人觉的没必要记忆,你只要知道有这些匹配器,在你的意识里有就可以了。当我们使用的多了,自然就记住了,并且会使用了。
继续学习Jest的匹配器,这节课我们就完成全部匹配器的学习,直接开始学习。
字符串包含匹配器,比如象牙山洗脚城有三个美女:谢大脚、刘英和小红,这时候我们要看看字符串中有没有谢大脚就可以使用toMatch()
匹配器。
test('toMatch匹配器',()=>{
const str="谢大脚、刘英、小红"
expect(str).toMatch('谢大脚')
})
因为确实有“谢大脚”,所以就通过测试了,当然你也可以写正则表达式。
test('toMatch匹配器',()=>{
const str="谢大脚、刘英、小红"
expect(str).toMatch(/谢大脚/)
})
我们可以开启yarn test
就可以看到测试结果也是正确的。
刚才我们使用的只是一个字符串包换关系的匹配器,但是在工作中使用的多是数组,所以这里我们使用数组的匹配器toContain()
。比如还是上面象牙山洗脚城的案例,我们就可以写成这样。
test('toContain匹配器',()=>{
const arr=['谢大脚','刘英','小红']
expect(arr).toContain('谢大脚')
})
当然他也可以完美的兼容set
的测试,比如把下面代码改为下面的方式。
test('toContain匹配器',()=>{
const arr=['谢大脚','刘英','小红']
const data = new Set(arr)
expect(data).toContain('谢大脚')
})
专门对异常进行处理的匹配器,可以检测一个方法会不会抛出异常。比如我们下面一段代码。
const throwNewErrorFunc = ()=>{
throw new Error('this is a new error')
}
test('toThrow匹配器',()=>{
expect(throwNewErrorFunc).toThrow()
})
我们也可以对这个匹配器中加一些字符串,意思就是抛出的异常必须和字符串相对应。
const throwNewErrorFunc = ()=>{
throw new Error('this is a new error')
}
test('toThrow匹配器',()=>{
expect(throwNewErrorFunc).toThrow('this is a new error')
})
如果字符串不匹配,也没办法通过异常测试。
not
匹配器是Jest
中比较特殊的匹配器,意思就是相反
或者说取反
.比如上面的例子,我们不希望方法抛出异常,就可以使用not
匹配器。
const throwNewErrorFunc = ()=>{
throw new Error('this is a new error')
}
test('toThrow匹配器',()=>{
expect(throwNewErrorFunc).not.toThrow()
})
现在这个测试用例就不能通过测试了,我们需要删除或注释掉抛出的异常,才可以通过测试。这就是not
匹配器的用法。
文章中我们只讲了最常用的一些匹配器,Jest
当中有很多匹配器,我不可能全部都讲到。所以这里给出官方文档,你可以自学。
你可以通过这个文档自学,或者在工作中需要时来查询一下。
目前我们的Jest是不支持import...from....
这种形式,如果使用就会报错,因为Jest默认支持的是CommonJS
规范,也就是Node.js
中的语法,他只支持require
这种引用。所以我们使用import...from...
是ES6的语法,所以使用就会报错。我们找到了报错的原因后,就非常好解决了,只要我们把import
形式转行成require
就可以了呗。
那我们先来看看错误的形式,也算带着大家踩个坑。
打开dabaojian.js
文件,然后把文件改成下面的样子.
export function baojian1(money){
return money>=200? '至尊享受':'基本按摩'
}
export function baojian2(money){
return money>=1000? '双人服务':'单人服务'
}
然后再打开dabaojian.test.js
文件,修改为下面的代码。
import {baojian1,baojian2} from './math.js'
test('保健1 300元',()=>{
expect(baojian1(300)).toBe('至尊享受')
})
test('保健2 2000元',()=>{
expect(baojian2(2000)).toBe('双人服务')
})
这时候你用yarn test
来测试,你会发现是没办法使用的,会报很多错误。这是因为我们需要用Babel进行转换,没有Babel转换是肯定会报错的。
其实解决这个问题,直接使用Babel就可以把代码转换成CommonJS
代码,然后就可以顺利进行测试了。那现在就来安装一下Babel
.
npm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
或者直接使用yarn来进行安装
yarn add @babel/core@7.4.5 @babel/preset-env@7.4.5 --dev
耐心等待babel
安装完成,然后打开package.json
文件,可以看到文件里有这样两行代码。
"devDependencies": {
"@babel/core": "^7.4.5",
"@babel/preset-env": "^7.4.5",
"jest": "^24.8.0"
},
看到这样的代码,说明Babel已经安装成功。然后我们还需要对Babel进行配置。
我们在项目根目录下新建一个.babelrc
的文件,作为一个前端,你对这个文件应该是非常熟悉的,babel的转换配置就写在这个文件里。
{
"presets":[
[
"@babel/preset-env",{
"targets":{
"node":"current"
}
}
]
]
}
当你写完这段代码后,就可以进行转换了。我们再次使用yarn test
进行测试,这时候就可以正确通过测试了。也就是说我们用的babel
起到作用了。
那为什么会这样那?其实在Jest
里有一个babel-jest
组件,我们在使用yarn test
的时候,它先去检测开发环境中是否安装了babel
,也就是查看有没有babel-core
,如果有bable-core
就会去查看.babelrc
配置文件,根据配置文件进行转换,转换完成,再进行测试。
在工作中我们很多方法都包含异步操作,所以测试异步代码成了工作中必不可少的一部分。这节课我们就讲一下如何通过Jest来测试异步代码。异步测试的内容非常多,所以我们可能分成几节课来讲。我们先来写一个异步代码测试的例子。
现在要作的事情是用axios
远程的请求我博客的数据。要使用axios
就需要先进行安装,直接使用npm
或yarn
进行安装就可以了。
npm 安装方法
npm install axios@0.19.0 --save
yarn安装方法
yarn add axioss@0.19.0
根据你的网络环境,安装速度会有所不同,但一般都会很快,也不会遇到什么难搞的事情。安装完成后,可以打开package.json
看一下安装结果和版本,我这里使用的是0.19.0
(如果你是其它版本,可能这个方法会出错,所以你最好和我使用一样的版本)。
因为异步请求,就需要一个远程的文件,技术胖为了方便你学习,提供如下远程地址(远程地址随时可能失效)。
安装好axios
以后,在项目根目录下面,新建一个文件fetchData.js
文件,然后编写代码如下:
import axios from 'axios'
export const fetchData = (fn)=>{
axios.get('http://a.jspang.com/jestTest.json').then((response)=>{
fn(response.data)
})
}
这是我们最经常使用的一种异步请求方法的形式,所以我们想来看看这种形式的测试代码如何编写。
有了这个文件,接下来就可以写测试文件fetchData.test.js
.在项目根目录下,新建一个fetchData.test.js
文件,然后编写下面的代码。
import { fetchData } from './fetchData.js'
test('fetchData 测试',()=>{
fetchData((data)=>{
expect(data).toEqual({
success: true
})
})
})
注意这样写是由问题的,因为方法还没有等到回调,我们的结果已经完成了,所以这时候你对于没测试完,只是方法可用,就返回了测试结果,这种结果是不保证正确的。
比如现在我们把请求的地址后边加一个1
,这时候才测试,依然是正确的。
axios.get('http://a.jspang.com/jestTest1.json').then((response)=>{
fn(response.data)
})
所以我们必须加入一个done方法,保证我们的回调已经完成了,这时候我们表示测试完成。
import { fetchData } from './fetchData.js'
test('fetchData 测试',(done)=>{
fetchData((data)=>{
expect(data).toEqual({
success: true
})
done()
})
})
这时候我们的测试代码才能保证测试准确无误的完成,所以你在工作中处理异步函数的时候,一定要注意这种异步函数的形式如何来进行测试。
上节课只是异步的一种方法,还有一种方法式直接返回一个promise
值,这样的方法在工作中也式经常使用的,所以这节课我们来看看如何测试这种方法。
打开上节课的fetchData.js
文件,然后编写下面的代码,具体代码我在视频中讲解。
export const fetchTwoData = ()=>{
return axios.get('http://a.jspang.com/jestTest.json')
}
从代码中可以看出,我们直接只用了Return
返回了异步请求的promise
值,这样的方法在实际工作中也经常使用,所以我们是有必要学习一下的,而且这里也是有坑的。
这些坑可能新手掉进去就很难趴出来。
打开fetchData.test.js
文件,然后新写一个测试用例,在写之前记得先把fetchTwoData
方法引过来。
import { fetchData , fetchTwoData } from './fetchData.js'
引入之后,编写测试用例,代码如下:
test('FetchTwoData 测试', ()=>{
return fetchTwoData().then((response)=>{
expect(response.data).toEqual({
success: true
})
})
})
这部分代码需要注意的是要使用return
才能测试成功,这个坑我学习的时候也踩到了,所以给大家说一下,希望大家不要踩坑。
在工作中有时候会测试异步接口不存在的需求(虽然不多,但这里有坑),比如有些后台需求不允许前台访问时,这时候就会返回404(页面不存在),这时候在测试时也存在一些坑,所以单独拿出来给大家讲一下。
继续打开fetchData.test.js
文件,然后编写测试代码如下,注意这个地址是不存在的,也就是返回404
。
export const fetchThreeData = ()=>{
return axios.get('http://a.jspang.com/jestTest_error.json')
}
这时候可能很多小伙伴说测试404直接用catch
就可以了,很简单,然后代码写成了这样。
test('FetchThreeData 测试', ()=>{
return fetchThreeData().catch((e)=>{
//console.log(e.toString())
expect(e.toString().indexOf('404')> -1).toBe(true)
})
})
但这样是错误的,比如现在我们把异步请求代码改正确后,我们再走一下测试,还是可以通过测试的。 现在把网址改成正确的:
那这是为什么那?因为测试用例使用了catch
方法,也就是说只有出现异常的时候才会走这个方法,而现在没有出现异常,就不会走这个测试方法,Jest就默认这个用例通过了测试。
这个也算是一个坑,想改这个坑也非常简单,只要使用expect.assertions(1)
就可以了,这个代码的意思是“断言,必须需要执行一次expect方法才可以通过测试”。
修改过后的代码就变成了这个样子
test('FetchThreeData 测试', ()=>{
expect.assertions(1) // 断言,必须执行一次expect
return fetchThreeData().catch((e)=>{
expect(e.toString().indexOf('404')> -1).toBe(true)
})
})
这时候测试用例就无法正常通过测试了,因为此时我们的地址是存在并正确返回结果的。我们需要改成错误的地址,才能通过测试。
我们会了正常的,也会了不正常的测试方法,组合起来用就会让测试变的强大的多,比如测试正常的时候是如何的,碰到不正常,应该是如何的。这也基本组成了我们异步测试的基本结构,就是不仅要测试正常情况,正常流程的代码,也要测试异常情况和突发情况下的代码健壮性。这节课就先到这里,下节课我们继续学习。
上面2节写异步测试用例时我使用了return
的形式,这只是其中的一种方法,还有另一种方法,就是使用async...await...
的这种语法形式来写测试用例。两种语法没有好坏之分,就看自己习惯和容易理解那种方法。
还是在上节课的文件fetchData.js
中,编写一个新的方法,代码如下:
export const fetchFourData = ()=>{
return axios.get('http://a.jspang.com/jestTest.json')
}
注意,这时候地址是正确的,也就是可以正常返回结果的。
这时候我们的代码使用async....await...
的形式,这里我们还使用了resolves
用于把现有对象转换成Promise
对象,然后使用Jest
中的toMatchObject
进行匹配对象中的属性。
test('FetchFourData 测试', async()=>{
//resolves把现有对象转换成Promise对象,
//toMatchObject 匹配对象中的属性
await expect(fetchFourData()).resolves.toMatchObject({
data:{
success:true
}
})
})
写完上面的代码就可以出正确的结果了,但是这种方法还是有点抽象的,需要用resolves
转换一下。有没有简单方法,答案是肯定的。我们可以把上面的测试方法写成这样。
test('FetchFourData 测试', async()=>{
const response = await fetchFourData()
expect(response.data).toEqual({
success : true
})
})
这就是用async...await...
来进行异步代码测试,希望小伙伴们可以自己练习一下,下节课我们继续学习。
疫情已经取得了阶段性的胜利,国内病例数马上下到两位数了。春光大好,万物复苏,技术胖又安奈不住自己荡漾的心情,所以就写一段“大宝剑”的程序,以解思苦。
在根目录下新建一个文件,起名为newBaoJian.js
,然后在文件中写入下面的代码,代码详细内容我会在视频中进行讲解。
export default class NewBaoJian {
gongzhu(number){
this.user = number==1?'大脚':'刘英'
}
anjiao(){
this.fuwu =this.user+'走进房间为你_足疗'
}
anmo(){
this.fuwu =this.user+'走进房间为你_按摩'
}
}
测试的方法,我们也可以这样写保证是正确的。
import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
test('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
test('测试 刘英按摩 方法',()=>{
baojian.gongzhu(2)
baojian.anmo()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_按摩')
})
beforeAll()
钩子函数的意思是在所有测试用例之前进行执行。
比如我们写一个这样的测试用例。
beforeAll(()=>{
console.log('吃完饭后,走进了红浪漫洗浴')
})
写完后,保存文件,会自动执行测试。这时候可以在控制台看到吃完饭后,走进了红浪漫洗浴
,最新执行啦。执行之后可以看到这句话最先执行了。然后才走下面的测试用例。
afterAll()
钩子函数是在完成所有测试用例之后才执行的函数。
afterAll(()=>{
console.log('有钱人的生活就是这么的枯燥且寂寞')
})
保存后,可以在控制台看到afterAll
是最后执行的。这个用于测试都完成后调用某个方法。
beforeEach()
钩子函数,是在每个测试用例前都会执行一次的钩子函数,比如我们写如下代码。
beforeEach(()=>{
console.log('给了300元钱后......')
})
保存后可以看到,每个测试用例执行之前都会先执行一下这个函数。
afterEach()
钩子函数,是在每次测试用例完成测试之后执行一次的钩子函数,比如下面的代码。
afterEach(()=>{
console.log('完成后,我心满意足的坐在沙发上!!!')
})
在工作中最常用的四个钩子函数就是这四个函数了,我这里举了一个简单且生动的例子,希望可以帮助小伙伴理解。为了小伙伴们学习方便,在下面我给出全部代码。
import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
beforeAll(()=>{
console.log('吃完饭后,走进了红浪漫洗浴')
})
beforeEach(()=>{
console.log('给了300元钱后......')
})
test('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
test('测试 刘英按摩 方法',()=>{
baojian.gongzhu(2)
baojian.anmo()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_按摩')
})
afterEach(()=>{
console.log('完成后,我心满意足的坐在沙发上!!!')
})
afterAll(()=>{
console.log('有钱人的生活就是这么的枯燥且寂寞')
})
这期视频就到这里了,下期视频我们学习一下如何对测试用例进行分组,下个视频继续啦!
继续上节课的代码,我们会发现服务项目太少了,根本没办法满足技术胖的需求,所以我要增加几个服务项目。
打开newBaoJian.js
文件,然后增加一个泰式保健
和宫廷御疗
,代码如下:
export default class NewBaoJian {
gongzhu(number){
this.user = number==1?'大脚':'刘英'
}
anjiao(){
this.fuwu =this.user+'走进房间为你_足疗'
}
anmo(){
this.fuwu =this.user+'走进房间为你_按摩'
}
taishi(){
this.fuwu =this.user+'走进房间为你_泰式保健'
}
gongting(){
this.fuwu =this.user+'走进房间为你_宫廷御疗'
}
}
接着上节课的测试用例newBaojian.test.js
继续进行编写新的代码,代码如下:
test('测试 大脚泰式保健 方法',()=>{
baojian.gongzhu(1)
baojian.taishi()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_泰式保健')
})
test('测试 刘英宫廷御疗 方法',()=>{
baojian.gongzhu(2)
baojian.gongting()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_宫廷御疗')
})
目前的全部测试用例代码如下:
import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
beforeAll(()=>{
console.log('吃完饭后,走进了红浪漫洗浴')
})
beforeEach(()=>{
console.log('给了300元钱后......')
})
test('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
test('测试 刘英按摩 方法',()=>{
baojian.gongzhu(2)
baojian.anmo()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_按摩')
})
test('测试 大脚泰式保健 方法',()=>{
baojian.gongzhu(1)
baojian.taishi()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_泰式保健')
})
test('测试 刘英宫廷御疗 方法',()=>{
baojian.gongzhu(2)
baojian.gongting()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_宫廷御疗')
})
afterEach(()=>{
console.log('完成后,我心满意足的坐在沙发上!!!')
})
afterAll(()=>{
console.log('有钱人的生活就是这么的枯燥且寂寞')
})
随着项目的不断增多,我们是时候出个套餐服务了,毕竟每个人的精力有限,不可能全部的服务都享受一下。所以就需要进行分组。
那最原始的方法是,我们新建两个测试文件,把大脚
的服务项放到一个测试文件里,把刘英
的测试文件放到一个文件里去,这就形成了分组。
但这样分组显然是不够优雅的,毕竟我们需要测试的代码在一个文件里,我们的测试文件却分成了两个文件。
其实Jest
为我们提供了一个分组的语法describe()
,这个方法接受两个参数,现在我们把大脚和刘英的测试用例用describe()
方法进行分开。代码如下:
import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
beforeAll(()=>{
console.log('吃完饭后,走进了红浪漫洗浴')
})
beforeEach(()=>{
console.log('给了300元钱后......')
})
describe('大脚相关服务',()=>{
test('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
test('测试 大脚泰式保健 方法',()=>{
baojian.gongzhu(1)
baojian.taishi()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_泰式保健')
})
})
describe('刘英相关服务',()=>{
test('测试 刘英按摩 方法',()=>{
baojian.gongzhu(2)
baojian.anmo()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_按摩')
})
test('测试 刘英宫廷御疗 方法',()=>{
baojian.gongzhu(2)
baojian.gongting()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_宫廷御疗')
})
})
afterEach(()=>{
console.log('完成后,我心满意足的坐在沙发上!!!')
})
afterAll(()=>{
console.log('有钱人的生活就是这么的枯燥且寂寞')
})
这时候我们结束一下测试,然后清一下屏幕,再进行测试,这时候你可以清楚看到代码是分组进行测试的。这样分组的好处实际就就是要让测试用例开起来更有层次感,也算为了下节课讲解生命周期的作用域作一个提前的铺垫。
其实上节课我们学习了describe
分组,就是为了讲解这节课的钩子作用域。Jest中钩子函数的作用域有下面三个特色。
为了更好的说明钩子函数的作用域,现在我们把程序的最外层加入一个describe
,其实不加这个,系统默认也是有这个的,只是不那么直观。
在newBaoJian.test.js
文件中加入describe
,代码如下:
import NewBaoJian from './newBaoJian'
const baojian = new NewBaoJian()
describe('最外层分组',()=>{
beforeAll(()=>{
console.log('吃完饭后,走进了红浪漫洗浴')
})
beforeEach(()=>{
console.log('给了300元钱后......')
})
describe('大脚相关服务',()=>{
test('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
test('测试 大脚泰式保健 方法',()=>{
baojian.gongzhu(1)
baojian.taishi()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_泰式保健')
})
})
describe('刘英相关服务',()=>{
test('测试 刘英按摩 方法',()=>{
baojian.gongzhu(2)
baojian.anmo()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_按摩')
})
test('测试 刘英宫廷御疗 方法',()=>{
baojian.gongzhu(2)
baojian.gongting()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('刘英走进房间为你_宫廷御疗')
})
})
afterEach(()=>{
console.log('完成后,我心满意足的坐在沙发上!!!')
})
afterAll(()=>{
console.log('有钱人的生活就是这么的枯燥且寂寞')
})
})
写完后你在控制台运行yarn test
,可以看到console.log
的顺序和结果并没有改变。并且每一个beforeEach
和afterEach
也都在每一个测试用例的前后执行了。这就是我们说的第一条钩子函数在父级分组可作用域子集,类似继承
现在“大脚”和“刘英”都希望在服务客人后有小费,但是价格不同。这时候就可以在两个同级的describe
中分别加入不同的afterEach
,比如大脚要30元小费,刘英要50元小费。
afterEach(()=>{
console.log('------大脚,你服务的很好,给你30元小费')
})
afterEach(()=>{
console.log('------刘英,你服务的很好,给你50元小费')
})
为了看的清楚,你可以暂时注释掉外层的四个钩子函数。这时候输出的结果就变成了。
console.log newBaoJian.test.js:27
大脚走进房间为你_足疗
console.log newBaoJian.test.js:41
------大脚,你服务的很好,给你30元小费
console.log newBaoJian.test.js:35
大脚走进房间为你_泰式保健
console.log newBaoJian.test.js:41
------大脚,你服务的很好,给你30元小费
console.log newBaoJian.test.js:52
刘英走进房间为你_按摩
console.log newBaoJian.test.js:64
------刘英,你服务的很好,给你50元小费
console.log newBaoJian.test.js:59
刘英走进房间为你_宫廷御疗
console.log newBaoJian.test.js:64
------刘英,你服务的很好,给你50元小费
这个案例也说明了钩子函数在同级的describe
分组里是互不干扰的。如果你说不好记忆,我这里再形象一点,举一个生活中的例子。
比如你有一个弟弟或者哥哥,意思你们是兄弟两个人,那你们的父母都是你们的父母,他们管理你们两个谁都好使,因为是长辈管教子辈。而你们长大了,各自娶了媳妇,你们兄弟两个的媳妇就不弄混了,自己就是自己的。
道理虽然粗鄙,但是能帮你更好的理解就好。
红浪漫现在又出了新的政策,原来都在大厅,现在却推出了包房服务。但是无论你要进入那个包房,你都必须先进入红浪漫洗浴。也就是说钩子函数要有个先后执行的关系。这个关系就是外部先执行,内部后执行。
现在把已经注释的外层的beforeAll
钩子函数注释去掉,然后在describe
中加入beforeAll
钩子函数。
beforeAll(()=>{
console.log('------然后走进了666号包房')
})
这时候你再看“控制台”的结果,就变成了下面的样子。
console.log newBaoJian.test.js:15
吃完饭后,走进了红浪漫洗浴
console.log newBaoJian.test.js:25
------然后走进了666号包房
console.log newBaoJian.test.js:31
大脚走进房间为你_足疗
console.log newBaoJian.test.js:45
------大脚,你服务的很好,给你30元小费
这时候为你让你看的清楚,我再第一个test测试用例这里加入一个only
,加入后,其它的用例都会skipped
掉,只执行这一个。
test.only('测试 大脚足浴 方法',()=>{
baojian.gongzhu(1)
baojian.anjiao()
console.log(baojian.fuwu)
expect(baojian.fuwu).toEqual('大脚走进房间为你_足疗')
})
这个例子正好说明了,外部的钩子函数先执行,下级的分组后执行,也就是执行顺序是“由外到内”的.only
的使用在工作中也是经常使用的,因为有时候测试用例很多,不好调试,就可以使用only
的形式单独调试。
No Data