由于现在前端涉及的东西越来越多,也越来越复杂,就对我们前端工程化能力提出了更高的要求。
好的前端工程化一般包括三个大的方面:
. 前端(自动化)测试(前提条件)
. 高质量的代码设计
. 高质量的代码实现
如果要想保证后两个 “高质量的代码设计” 和 “高质量的代码实现”,就必须要有前端自动化测试。
一、测试包括
=> 单元测试
单元测试,调用被测试的类或方法,根据类或方法的参数,传入相应的数据。然后,得到一个返回结果。最终断言返回的结果是否等于预期结果。如果相等,测试通过;如果不相等,测试失败。所以,这里单元测试关注的是代码的实现与逻辑。
=> 接口测试
接口测试,根据接口文档,到底是传get请求呢?还是post请呢?调用被测试的接口,构造相应的数据(id=1,name=‘xiaoming’),得到返回值,是200成功,并返回查询结果。还是10021,用户名不能为空。不管输入的参数是怎样的,我们都将得到一个结果。最终断言返回的结果是否等于预期结果。如果相等,测试通过;如果不相等,测试失败。所以,接口测试关注的是数据。只要数据正确了,功能就做成大半,剩下的无非是如何把这些数据展示在页面上。
=> web测试
web测试更贴近用户的行为,模拟用户点击了某个按钮,向个输入框里输入了什么。用户可以看到登录成功了,但web自动化测试并不知道它刚才的点击有没有生效。所以,要找“证据”,比如,登录成功后页面右上角会显示“欢迎,xxx”。这就是登录成功的有力“证据”。于是,当web自动化测试登录成功后,就去获取这个数据进行断言。断言如果相等,测试通过;如果不相等,测试失败。所以,web测试关注点是用户操作行为,页面上真正的按钮和输入框是否可用。
二、单元测试的定义
单元测试(unit testing),是指对软件中的最小可测试单元进行检查和验证。
在前端就是指一个模块,并且在前端最小必须是一个模块,如果不是模块就没有办法导出,就无法进行测试。
什么时候进行单元测试:程序员们对自己开发的每个功能进行单元测试。
单元测试对应的集成测试。
集成测试:又叫组装测试或者联合测试。 在做完单元测试的基础上还要做一个集成测试,对几个相关联功能点一起测试,不仅限于一个模块。或者将所有模块按照设计要求组装成一个子系统或者完整的系统进行集成测试。
什么时候进行集成测试:代码和团队合并后,把整个代码进行集成测试,或者在项目要“竣工”的时候,就要进行集成测试。
三、单元测试的目的
1)验证代码与设计相符合
2)跟踪需求和设计的实现
3)发现设计和需求中存在的错误
4)发现编码过程中引入的错误
四、单元测试工作原理
测试是如何进行的:编写测试用例,测试用例当中最主要的是测试步骤和预期结果;测试人员根据测试用例执行操作步骤,然后通过眼睛和思考判断实际结果与预期结果是否相等。如果相等,测试通过;如果不相等,测试失败。
手工测试与自动化测:
从测试的行为本质上来看,手工测试与自动化测试并没有区别。主要的区别是,一个由人来执行,一个由代码或工具执行。快慢的问题。
五、前端单元测试主流框架介绍
目前主流的前端测试框架:Jasmine、Mocha、Jest,三足鼎立。还有很多就不一一列举了。
Jasmine:比较老的一个js测试框架,据说过去也曾很风流。
Mocha:诞生于2011年,也是一个主流的前端测试框架,有着丰富的功能,但是很多功能需要添加其他的插件库,成本比较高。
(Mocha是一个独立的开源项目,由志愿者独家维护。版权所有2011-2020 OpenJS基金会和贡献者。许可麻省理工学院。)
Jest:是由Facebook发布的开源的前端测试框架,是目前最流行的前端测试框架,是一个比较新的框架,在程序领域,一般情况下新出的东西都要比老的好用一点,大部分是这样的,要不然怎么在江湖上混。目前国内很多的大型互联网公司大厂都在用jest,所以我也选择了jest框架,下面演示的 demo 案例也是用的jest。
六、jest框架优点介绍
jest框架内置了常用的测试工具,比如自带断言、测试覆盖率工具,实现了开箱即用。不需要额外配置添加其他的插件,集成了 Mocha,chai,jsdom,sinon等功能,对react组件支持度非常友好, 同时它也支持Babel、 TypeScript、 Node、 React、 Angular、 Vue 等!,自带snapshot(快照)功能,成本较低。
snapshot功能:能够确保UI不会意外被改变。Jest会把结果值保存在一个文档中,每次进行测试的时候会把测试值与文件中的结果进行比较,如果两个结果值不同,那么开发者可以选择要么改变代码,要么替代结果文件。
三大优点: 比较新(刚出来不久)、 基础好(性能好 功能多 简单易用)、 速度快。
七、jest框架Demo演示
1、确保node 和npm已安装
2,创建项目文件夹jesttest
3,创建 package.json 文件
在项目文件夹里 执行命令 =》 npm init 创建 package.json 文件。并将 scripts.test 修改为如下所示。
{
"name": "jesttest",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "jest"
},
"author": "",
"license": "ISC",
"devDependencies": {
"jest": "^24.8.0"
}
}
4、安装jest框架
执行命令 =》npm install jest@24.8.0 -D
5、创建被测试文件
新建文件math.js(被测试文件),并且写一些业务逻辑方法,如下代码所示。
// math.js
function add(a, b){
return a + b;
}
function sub(a, b){
return a - b;
}
module.exports={
add, sub
}
6、创建测试文件
再新建文件math.test.js(测试文件),写一些测试用例,如下代码所示。
// math.test.js
const math = require('./math.js')
const {add, sub} = math
test('加法测试', () => {
expect(add(1,6)).toBe(7)
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
})
test方法:jest封装的测试方法,一般填写两个参数,描述和测试方法。
expect方法 :预期方法,就是你调用了什么方法,传递了什么参数,得到的预期是什么。
toBe方法:匹配器(Matchers),测试输入输出的值是否符合预期。(继续往下看,后面有常用匹配器的介绍)。
7、进行测试
执行命令 =》npm run test 看结果。
开启自动测试:
在package.json文件里设置。修改如下:
"scripts": {
"test": "jest --watchAll"
},
修改保存后,我们在终端再次运行npm run test,这时候测试一次后,它并没有结束,而是等待测试文件的变化,只要文件发生变化,ctrl+s保存后就会自动进行测试。
8,如何查看代码测试覆盖率
代码测试覆盖率:就是测试的代码对功能性的代码和业务逻辑代码做了百分之多少的测试。这个百分比就是代码测试覆盖率。
步骤:
a,Jest初始化配置:
执行命令 =》 npx jest --init
初始化完成后,根目录会出现一个 jest.config.js 文件。
b,生成代码覆盖率:
这是终端直观展示,便于开发人员查看测试结果。 在执行完 npx jest --coverage 命令后会在根目录自动生成一个 coverage 文件夹,里面 index.html 文件是一个网页版的测试结果报表,可以展示给客户或者领导看。
注意:在 jest.config.js 文件中,配置项 coverageDirectory: "coverage" 的属性值 "coverage" 不是固定的,是可以自定义的,是用来定义生成报告文件夹的名字的。
八、jest框架常用匹配器:
匹配器是jest中非常重要的一个概念,使用不同匹配器可以测试输入输出的值是否符合预期,它提供了很多匹配规则,理解它类似于js的逻辑运算符。
toBe()
相等,相当于===,也就是我们常说的严格相等。引用地址不同,也会测试失败。
(在刚才的案例中我们就用的这个匹配器)
toEqual()
相等,相当于==。
toBeNull()
匹配器只匹配null值,需要注意的是不匹配undefined的值。
toBeUndifined()
我们要匹配undefined时,就可以使用toBeUndifined()匹配器。
toBeDefined()
toBeDefined()匹配器的意思是只要定义过了,都可以匹配成功。这里需要注意的是,给一个null值也是可以通过测试的。
toBeTruthy()
这个是true和false匹配器,就相当于判断真假的。
oBeFalsy()
跟toBeTruthy()对应的就是toBeFalsy(),这个匹配器只要是返回的false就可以通过测试。
toBeGreaterThan()
这个是用来作数字比较的,大于什么数值,只要大于传入的数值,就可以通过测试。
toBeLessThan()
toBeLessThan跟toBeGreaterThan相对应的,就是少于一个数字时,就可以通过测试。
toBeGreaterThanOrEqual()
当测试结果数据大于等于时,就可以通过测试。
toBeLessThanOrEqual()
这个跟toBeGreaterThanOrEqual()相对应。
toBeCloseTo()
这个是可以自动消除JavaScript浮点精度错误的匹配器,举个例子,比如我们让0.1和0.2相加,这时候js得到的值应该是0.30000000000004,所以如果用toEqual()匹配器,测试用例会通过不了测试的。这时候我们就需要使用toBeCloseTo()匹配器,可以顺利通过测试。
toMatch()
字符串包含匹配器,比如“HTTP Status 404”,这时候我们要看看字符串中有没有“404”就可以使用toMatch()匹配器。
toContain()
数组包含匹配器。比如[1,2,3],这时候我们要看看这个数组中有没有3,就可以使用toContain()匹配器。
toThrow()
专门对异常进行处理的匹配器,可以检测一个方法会不会抛出异常。
not匹配器
not匹配器是Jest中比较特殊的匹配器,意思就是相反或者说取反。
关于匹配器的学习文档地址:https://jestjs.io/docs/en/expect
九、 jest框架常用的四个钩子函数
beforeAll()
在所有测试用例之前进行执行。
afterAll()
是在完成所有测试用例之后才执行的函数。
beforeEach()
钩子函数,是在每个测试用例前都会执行一次的钩子函数。
afterEach()
是在每次测试用例完成测试之后都会执行一次的钩子函数。
// math.test.js
const math = require('./math.js')
const { add, sub, addMix, subMix } = math
beforeAll(()=>{
console.log('月考测试开始')
})
beforeEach(()=>{
console.log('开始计算这道题。。。。')
})
test('加法测试', () => {
expect(add(1,6)).toBe(7)
console.log('加法题已算完')
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
console.log('减法题已算完')
})
afterEach(()=>{
console.log('接着算下一道题。。。。')
})
afterAll(()=>{
console.log('月考测试结束')
})
执行命令 =》npm run test 看结果。
十、 Jest框架测试用例分组
Jest对测试用例提供了进行分组的方法describe()
// math.test.js
const math = require('./math.js')
const { add, sub, addMix, subMix } = math
beforeAll(()=>{
console.log('月考测试开始')
})
beforeEach(()=>{
console.log('开始计算这道题。。。。')
})
describe('一年级考试',()=>{
test('加法测试', () => {
expect(add(1,6)).toBe(7)
console.log('加法题已算完')
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
console.log('减法题已算完')
})
})
describe('二年级考试',()=>{
test('加法混合运算测试', () => {
expect(addMix(2, 3, 4)).toBe(9)
console.log('加法混合运算已算完')
})
test('减法混合运算测试', () => {
expect(subMix(10,5,2)).toBe(3)
console.log('减法混合运算已算完')
})
})
afterEach(()=>{
console.log('接着算下一道题。。。。')
})
afterAll(()=>{
console.log('月考测试结束')
})
执行命令 =》npm run test 看结果。十一、jest钩子函数作用域
三个特色:
1,钩子函数在父级分组可作用域子集,类似继承
2,钩子函数同级分组作用域互不干扰,各起作用
3,先执行外部的钩子函数,再执行内部的钩子函数
为了更好的说明钩子函数的作用域,现在我们把程序的最外层加入一个describe,其实不加这个,系统默认也是有这个的,只是不那么直观。
1,钩子函数在父级分组可作用域子集,类似继承,代码说明如下:
// math.test.js
const math = require('./math.js')
const { add, sub, addMix, subMix } = math
describe('最外层分组',()=>{
beforeAll(()=>{
console.log('月考测试开始')
})
beforeEach(()=>{
console.log('开始计算这道题。。。。')
})
describe('一年级考试',()=>{
test('加法测试', () => {
expect(add(1,6)).toBe(7)
console.log('加法题已算完')
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
console.log('减法题已算完')
})
})
describe('二年级考试',()=>{
test('加法混合运算测试', () => {
expect(addMix(2, 3, 4)).toBe(9)
console.log('加法混合运算已算完')
})
test('减法混合运算测试', () => {
expect(subMix(10,5,2)).toBe(3)
console.log('减法混合运算已算完')
})
})
afterEach(()=>{
console.log('接着算下一道题。。。。')
})
afterAll(()=>{
console.log('月考测试结束')
})
})
执行命令 =》npm run test 看结果。通过打印的结果,我们看到父级的每一个beforeEach和afterEach也都在子集的每一个测试用例的前后执行了。这就是我们说的第一条钩子函数在父级分组可作用域子集,类似继承。
2,钩子函数同级分组作用域互不干扰,各起作用,代码说明如下:
// math.test.js
const math = require('./math.js')
const { add, sub, addMix, subMix } = math
describe('最外层分组',()=>{
// beforeAll(()=>{
// console.log('月考测试开始')
// })
// beforeEach(()=>{
// console.log('开始计算这道题。。。。')
// })
describe('一年级考试',()=>{
beforeEach(()=>{
console.log('一年级小明你是最棒的。。。。')
})
test('加法测试', () => {
expect(add(1,6)).toBe(7)
console.log('加法题已算完')
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
console.log('减法题已算完')
})
})
describe('二年级考试',()=>{
beforeEach(()=>{
console.log('二年级小强你是最优秀的。。。。')
})
test('加法混合运算测试', () => {
expect(addMix(2, 3, 4)).toBe(9)
console.log('加法混合运算已算完')
})
test('减法混合运算测试', () => {
expect(subMix(10,5,2)).toBe(3)
console.log('减法混合运算已算完')
})
})
// afterEach(()=>{
// console.log('接着算下一道题。。。。')
// })
// afterAll(()=>{
// console.log('月考测试结束')
// })
})
执行命令 =》npm run test 看结果。这个案例输出的结果也说明了钩子函数在同级的describe分组里是互不干扰的。
3,先执行外部的钩子函数,再执行内部的钩子函数,代码说明如下:
// math.test.js
const math = require('./math.js')
const { add, sub, addMix, subMix } = math
describe('最外层分组',()=>{
beforeAll(()=>{
console.log('月考测试开始')
})
// beforeEach(()=>{
// console.log('开始计算这道题。。。。')
// })
describe('一年级考试',()=>{
beforeEach(()=>{
console.log('一年级小明你是最棒的。。。。')
})
test('加法测试', () => {
expect(add(1,6)).toBe(7)
console.log('加法题已算完')
})
test('减法测试', () => {
expect(sub(5,3)).toBe(2)
console.log('减法题已算完')
})
})
describe('二年级考试',()=>{
beforeEach(()=>{
console.log('二年级小强你是最优秀的。。。。')
})
test('加法混合运算测试', () => {
expect(addMix(2, 3, 4)).toBe(9)
console.log('加法混合运算已算完')
})
test('减法混合运算测试', () => {
expect(subMix(10,5,2)).toBe(3)
console.log('减法混合运算已算完')
})
})
// afterEach(()=>{
// console.log('接着算下一道题。。。。')
// })
afterAll(()=>{
console.log('月考测试结束')
})
})
执行命令 =》npm run test 看结果。这个案例也说明了先执行外部的钩子函数,再执行内部的钩子函数。
十二、让jest支持import...from...语法
目前我们的Jest是不支持import...from....这种形式,如果使用就会报错,因为Jest默认支持的是CommonJS规范,也就是Node.js中的语法,他只支持require这种引用。所以我们使用import...from...是ES6的语法,所以使用就会报错。只要我们把import形式转行成require就可以了。
1,安装Babel转换器
直接使用Babel就可以把代码转换成CommonJS代码,然后就可以顺利进行测试了。
执行命令=》 npm install @babel/core@7.4.5 @babel/preset-env@7.4.5 -D
2,Babel基本配置
我们在项目根目录下新建一个.babelrc的文件。
{
"presets":[
[
"@babel/preset-env",{
"targets":{
"node":"current"
}
}
]
]
}
为什么会这样呢?其实在Jest里有一个babel-jest组件,我们在使用npm run test的时候,它先去检测开发环境中是否安装了babel,也就是查看有没有babel-core,如果有bable-core就会去查看.babelrc配置文件,根据配置文件进行转换,转换完成,再进行测试。
十三. 异步代码测试方法
1, 回调函数式
a.安装axios: install axios@0.19.0 –save。
c.创建 fetchData.test.js文件,测试用例代码如下。
// fetchData.test.js
import {fetchData, fetchData2} from './fetchData.js';
test('fethData测试', (done) => {
fetchData((data)=>{
expect(data.isSuccess).toEqual(true)
done()
})
})
在这里需要注意的是,写测试用例的时候一定要记得加 done方法, 用来保证回调完成。如果不加done的话出现的现象是,还没有等到回调,结果就完成了,这种结果是不保证正确的。
2、直接返回promise
b.在fetchData.test.js文件编写测试用例代码如下。
// fetchData.test.js
import {fetchData, fetchData2} from './fetchData.js';
// test('fethData测试', (done) => {
// fetchData((data)=>{
// expect(data.isSuccess).toEqual(true)
// done()
// })
// })
test('fetchData2测试', ()=> {
return fetchData2().then((response) => {
// console.log(response.data)
expect(response.data.isSuccess).toEqual(true)
})
})
在这里注意的是,测试的时候一定要记得加 return,不加return 也会没跑完就返回结果,测试结果无法保证正确。
3, 测试接口是否存在
比如有些后台需求不允许前台访问时,这时候就会返回404(页面不存在),这时候再测试时也存在一些坑。
b.在fetchData.test.js文件编写测试用例代码如下。
// fetchData.test.js
import {fetchData, fetchData2, fetchData3} from './fetchData.js';
// test('fethData测试', (done) => {
// fetchData((data)=>{
// expect(data.isSuccess).toEqual(true)
// done()
// })
// })
// test('fetchData2测试', ()=> {
// return fetchData2().then((response) => {
// // console.log(response.data)
// expect(response.data.isSuccess).toEqual(true)
// })
// })
test('fetchData3测试', () => {
expect.assertions(1)
return fetchData3().catch((e) => {
// console.log(e.toString())
expect(e.toString().indexOf('404')>-1).toEqual(true)
})
})
在这里注意的是,测试的时候一定要加上断言expect.assertions(1),表示必须走一遍expect(),测试用例才通过,不走的话即使显示测试通过,但测试结果也无法保证正确。
4,async...await... 写法
上面案例写异步测试用例时使用了return的形式,这只是其中的一种方法,还有另一种方法,就是使用async...await...的这种语法形式来写测试用例。两种语法没有好坏之分,就看自己习惯和容易理解那种方法。
b.在fetchData.test.js文件编写测试用例代码如下。
// fetchData.test.js
import {fetchData, fetchData2, fetchData3, fetchData4} from './fetchData.js';
// test('fethData测试', (done) => {
// fetchData((data)=>{
// expect(data.isSuccess).toEqual(true)
// done()
// })
// })
// test('fetchData2测试', ()=> {
// return fetchData2().then((response) => {
// // console.log(response.data)
// expect(response.data.isSuccess).toEqual(true)
// })
// })
// test('fetchData3测试', () => {
// expect.assertions(1)
// return fetchData3().catch((e) => {
// // console.log(e.toString())
// expect(e.toString().indexOf('404')>-1).toEqual(true)
// })
// })
test.only('fetchData4测试', async() => {
const response = await fetchData4()
expect(response.data.isSuccess).toEqual(true)
})
十四、dist框架only的用法
在测试的时候,经常会一个测试文件里写很多个测试用例,有时候我们只想让某一个测试用例执行,这时候我们就可以使用only指定哪个执行,其他的会自动跳过,只执行这一个,如下图所示。十五、TDD 和 BDD
TDD
Test Driven Development 测试驱动的开发
关注需求,深入业务,把业务需求最简化,迭代到细节。
BDD
Behavior Driven Development 行为驱动开发
关注反应,用户行为会产生什么反应,反应是否正确。
BDD帮助开发人员设计(design)软件,TDD帮助开发人员测试(test)软件。
以上内容是我本次入门单元测试的学习笔记。
通过查阅文档和网上各路大神贡献的博客以及视频教程,总结出来的一篇学习笔记,知识来源于网络,再给人家还回去,算是礼尚往来。如有需要改正/补充的地方,或者对你有所帮助,欢迎留言一起探讨。