顶级测试框架Jest指南:跑通一个完美的程序,就是教出一群像样的学生
facebook三大项目:yarn jest metro,有横扫宇宙之势。 而jest项目的宗旨为:减少测试一个项目所花费的时间成本和认知成本。 ——其实,它在让你当一个好老师。
jest文档非常简略、难以阅读, 因此才有了这篇文章。 jest是vue、react和vue-cli技术栈的重要一环,也是当前最值得掌握的测试框架,对此你需要达到很熟悉的程度。
本文github地址: https://github.com/wanthering...
教育和测试,是相通的。
你可以野路子自学,但掌握系统化、体系化的知识,终归离不开一个好老师。
测试可以不写,但当你面对大型复杂的项目之时,没有测试框架寸步难行。
现在,你可以跟我一步一步学会jest,你将懂得:为什么jest是优雅、简洁而符合人性的,并且终将成为测试界的唯一的、最佳的解决方案。
第一课:jest的初步使用
想象一下,当你走上讲台,五十多个孩子静静地看着你。红扑扑的脸蛋,大大的眼睛。
接下来,你将课本上知识教给他们了一遍,
函数运行过程,就相当于你的教学过程。
请同学们跟我一起敲下代码:
(请在项目下创建一个文件:lesson1.js
)
lesson1.js
/**
* 加法,即是将多个数值逐个累加
*/
exports.sum = (...args) => {
let res = 0
for (let i of args) {
res += i
}
return res
}
/**
* 乘法,即是将b个重复的数值a,进行累加
*/
exports.times = (a, b) => {
let resArr = (new Array(b)).fill(a)
return exports.sum(...resArr)
}
以上文件涉及es6和nodejs模块化的基础知识,初看可能并不好理解,但相信我,你敲完以下的测试代码,就会理解它的含义——就像教会一个学生一样。
就像你js工程师面临的永恒难题一样——真的能跑通吗?
当老师的你,内心也无比惶恐,第一次教这些,他们真的能懂吗?
这时需要用上jest进行测试,使用npm安装:
npm i jest -S
一个合格的老师,会做这几个步骤:
- 提问: 清晰地注明问题.
- 叫人回答: 给予一个函数体,运行测试。
- 期待答案: expect()
- 校验答案: toBe()、toEqual()
创建一个lesson1.test.js
lesson1.test.js
const {sum, times} = require('./lesson1')
test('同学,请问这个累加的结果是什么?',()=>{
expect(sum(2+2+2+2+2+2)).toBe(12)
})
test('同学,请问这个乘法的结果是什么?',()=>{
expect(times(2,6)).toBe(12)
})
test('那么,两个数值的结果是否相等呢?',()=>{
expect(times(2,6)).toEqual(sum(2+2+2+2+2+2))
})
运行测试方法1:
这时需要运行jest命令,你可以全局安装jest:
npm i jest -g
然后
jest lesson1.test.js
运行测试方法2:
也可以在package json的test字段下写入
...
"scripts": {
"test": "jest"
},
...
然后命令行输入
npm test
运行测试方法3:
如果你使用的webstorm等IDE的话,文档中直接显示绿色小键头,点击就是了。
测试结果:
接下来,会看到一路pass,证明lesson1.js文件无误。
我们读书这么多年,深知好老师和坏老师的区别:
上课照本宣科,就像直接写下满纸js代码一样,这是赖皮狗老师的专长。
但一个优秀的老师,他会:
- 步步为营: 分解成很多个子测试,把知词点逐个击破。
- 版书清晰: 标明需要测试什么,让知识易学易记。
- 课堂互动: 回调函数中运行测试,调动孩子们的课堂积极性。
- 题型丰富: 通过匹配器,即toBe、toEqual等校验不同格式数据。
如果你不是很熟悉jest框架提供的各类匹配器,这里有一份小抄送给你:jest-cheat-sheet
你看,jest所做的,没有一丝多余步骤,也没少一个必要步骤,这正是我们这些年遇到的好老师的共有特征,也正是jest测试的极致之处。
第二课:异步测试获取数据
jest在异步过程中也非常方便!
我们先使用json-server
建立服务器,再使用axios
获取数据。
这两个npm包使用非常广泛,熟悉的axios封装的可以直接拷贝server.js
和request.js
代码。
下载:
npm i json-server axios -S
创建server.js文件
server.js
const jsonServer = require('json-server')
const defaultData = () => ({
'lesson': [
{ 'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang' },
{ 'id': 2, 'title': 'how to multiply', 'teacher': 'Mr Liu' },
{ 'id': 3, 'title': 'how to subtract', 'teacher': 'Ms Han' }
],
'homework': [
{ 'id': 1, 'works': ['add','multiply'], 'student': 'Jim Green' },
{ 'id': 2, 'title': ['add','subtract'], 'student': 'lily' },
{ 'id': 3, 'title': ['add','subtract'], 'student': 'lucy' },
{ 'id': 4, 'title': ['add','subtract','multiply'], 'student': 'Han Mei Mei' },
{ 'id': 5, 'title': ['subtract','multiply'], 'student': 'Li Lei' }
],
'exam': { 'name': 'primary school final exam' }
})
const createJSONServer = (data = defaultData()) =>{
const server = jsonServer.create()
const router = jsonServer.router(data)
const middlewares = jsonServer.defaults()
server.use(middlewares)
server.use(router)
return server
}
createJSONServer().listen(3000)
console.log('3000端口已联通')
创建request.js文件
request.js
const axios = require( 'axios' )
class HttpRequest {
constructor(baseUrl = 'http://localhost:3000') {
this.baseUrl = baseUrl
}
/**
* 返回默认配置
*/
getInsideConfig() {
return {
baseURL: this.baseUrl,
headers: {}
}
}
/**
* 响应栏截,返回指定格式信息
*/
interceptors(instance) {
instance.interceptors.response.use(res => {
const {data} = res
return data
}, error => {
return Promise.reject(error)
})
}
/**
* 处理网络请求
*/
request(options) {
const instance = axios.create()
options = Object.assign(this.getInsideConfig(), options)
this.interceptors(instance)
return instance(options)
}
}
/**
* 导出request模块
*/
exports.requestPromise = (port,method='get')=>{
const Http = new HttpRequest('http://localhost:3000')
return Http.request({
url: port,
method
})
}
exports.requestCallback = (cb,port,method='get')=>{
const Http = new HttpRequest('http://localhost:3000')
Http.request({
url: port,
method
}).then(data=>{
// ▽请注释掉下面一行,再试一试回调能否跑通?
cb(data)
})
}
使用node server
跑起服务,我们就可以开始测试异步回调了。
老师的困扰: 坏学生“异步回调函数”
班上来了一个坏学生,上课从不好好听课,偏偏人缘又特别好。
这时,你提问让坏学生回答,他只是安静地站起来,什么都不做,然后过了一会对你说:“老师,这道题我回答过了啊!”
同时,全班同学都点头“嗯嗯他回答过了”。
这时候,你,该怎么办? 有没有感受到深深的绝望?
看一看以下的例子。创建lesson2.test.js,并写入
lesson2.test.js
const {requestPromise, requestCallback} = require('./request')
test('异步callback方式检测', () => {
// 下面进行了抛出了两次断言,在断言之前可以
function callback (data){
expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
}
requestCallback(callback,'lesson/1')
})
跑通了,似乎一切正常。。。
但当我们回到request.js
注释掉cb(data)
时,我们知道回调函数并不返回数据,测试理应不通过。然而。。
还是跑通了!
这是因为requestCallback根本就没有进入函数体,而测试函数,只要不报错,都算通过。
异步回调,就是这样一个坏透了的学生,经常装作回答过了蒙混过关。
异步回调的正确测试方法:
作为老师,弄死坏学生的方法可能你自己想到了:
“上黑板写,写完告诉我!”
你可以提供一个在测试函数内提供一个done,done必须要调用后,才能算做测试通过。
lesson2.test.js
test('异步callback方式检测', done => {
// 下面进行了抛出了两次断言,在断言之前可以
function callback (data){
expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
done()
}
requestCallback(callback,'lesson/1')
})
这样,就必须要运行了callback才能通过测试了,否则就会超时报错。
除了以上方式以外,你还可以检验函数体内的断言跑了几次
test('异步callback方式检测,无done', () => {
// 下面进行了抛出了两次断言,在断言之前可以
expect.assertions(1);
function callback (data){
expect(data).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
}
requestCallback(callback,'lesson/1')
})
expect.assertions(1)
表示, 断言语句expect(xxx).toXXX(xxx)
必须跑通一次,将要检验多个“坏学生”异步回调的时候,这招犹其有效。
普通学生:异步Promise
有坏学生,自然有好学和普通学生,promise就是一名“普通的学生”
lesson2.test.js
test('异步Promise方式检测', () => {
expect.assertions(1);
return requestPromise('exam').then(data => {
expect(data).toStrictEqual({'name': 'primary school final exam'})
})
})
检验异步Promise时,必须要用return
返回,否则它就像“坏学生”一样,直接蒙混过关溜走了。
你还可以使用使用.resolve/.reject形式
lesson2.test.js
test('异步Promise方式被成功resolve', () => {
expect.assertions(1);
return expect(requestPromise('exam')).resolves.toStrictEqual({'name': 'primary school final exam'})
});
如果需要检验Promise被reject:
test('异步Promise方式被reject', () => {
expect.assertions(1);
return expect(requestPromise('exam')).rejects.toMatch('error')
});
三好学生:async/await异步
上课从不迟到、校服永远一尘不染、作业按时永远上交、上课认真做笔记、回答问题天衣无缝、别人不听课她还会打!小!报!告!
这就是我们的async/await,我们应该多一些这样的三好学生。
lesson2.test.js
test('异步async/await方式检测', async () => {
// 下面进行了抛出了两次断言,在断言之前可以
expect.assertions(2)
const lesson1 = await requestPromise('lesson/1')
const homework3 = await requestPromise('homework/3')
expect(lesson1).toStrictEqual({'id': 1, 'title': 'how to add', 'teacher': 'Miss Wang'})
expect(homework3).toMatchObject({'student': 'lucy'})
})
优雅,舒适、简洁、大方,无需多言,async/await值得你拥有。
虽然我们很多时候还是在和异步回调、异步Promise打交道...
第三课:Mock函数,袖口五道杠的风纪委员
假如不是简单的测试同步返回、测试异步返回,而是需要记录执行过程中的状态呢?
const forEach = (arr, fn) => {
if (!arr.length || !fn) return
let i = -1
let len = arr.length
while (++i < len) {
let item = arr[i]
fn(item, i, arr)
}
}
假如遇到这样一个循环函数,内部的运行状态就不太可能通过返回值来知晓了。这时,你需要Mock函数。
Mock函数,随时记录函数运行状态
测试同步返回值、异步返回值,就像是上课,这只是老师的本份。
而学生大部分时间,都是自习、吃饭、宿舍,老师想管到这一部分,就需要派出让人闻风丧脸的存在——五道杠·无间道·小报告之王·风纪委员。
她平时混迹在普通学生之中,或者说,她就是一名再普通不过的学生。但,当她遇到老师——即在test()测试体内——一点儿陈芝麻烂谷的破事,都会被抖露出来。
lesson3.test.js
//通过jest.fn()创建Mock Function
const mockCallback = jest.fn(x => 42 + x);
//将mockCallback代入forEach运行一次,即可记录下所有的值
forEach([0, 1], mockCallback);
test('记录mockCallback函数运行过程',()=>{
// mockCallback上报函数运行了两次
expect(mockCallback.mock.calls.length).toBe(2);
// mockCallback上报函数第一次运行的输入为0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// mockCallback上报函数第二次运行的输入为1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// The return value of the first call to the function was 42
// mockCallback上报函数第一次运行的结果为42
expect(mockCallback.mock.results[0].value).toBe(42);
})
结语
提起教师,随处可见的朋友圈、公众号、贺卡
短信里面,尽是鸡汤般的口号:“人类灵魂工程师”、“燃烧自己,照亮他人”“无私”“奉献”“爱心”。
但,教师的本职——最高效率地辅助学生构建的完整知识体系。 专业性的观点却被选择性忽视。
软件开发也是,人人在谈测试驱动,人人都在强调测试的重要性,测试似乎成了一个形而上学的感性概念。
但时间和人力成本上、框架的专业性、框架的掌握成本的诸多限制,使大多数项目测试相当于无。另一方面,少数高度测试覆盖的项目又显得十分笨拙,测试耗费的精力居然比coding还长...
幸之,得益于jest测试框架的产生,一个极致简洁
功能强大、语义清晰的测试终于呈现在我们面前。让开发与测试相辅相成,而非时间加倍。
而最新的vue技术栈正在全面采用jest测试框架。
从此,testing之对于coding,如同孤独的钢琴曲中,缓缓传来小提琴的和弦,顿时锦瑟和鸣,心灵震颤无以复加。
前面我已经介绍了AST抽象语法树,点击链接地址,下一期,将结合AST带来测试驱动开发实战,这,将是读懂vue-cli3、并掌握vue全方位技术栈的第一步。
vue-cli3技术栈补全预告:
- [x] AST抽象语法树: 童年的玩具
- [x] jest测试框架: 行为人师,学为世范
- jest测试驱动开发实战: js配置文件的更新
- yaml配置文件: 至爱之信
- config文件导入全配置: 万能钥匙
- vue-share-utils:实用小工具
- Generator: vue-cli3核心引擎,机械之心
- jest+Generator: 测试驱动vue-cli3引擎
来源:https://segmentfault.com/a/1190000016399447
顶级测试框架Jest指南:跑通一个完美的程序,就是教出一群像样的学生的更多相关文章
- 前端测试框架Jest系列教程 -- Mock Functions
写在前面: 在写单元测试的时候有一个最重要的步骤就是Mock,我们通常会根据接口来Mock接口的实现,比如你要测试某个class中的某个方法,而这个方法又依赖了外部的一些接口的实现,从单元测试的角度来 ...
- 前端测试框架Jest系列教程 -- Mock Functions(模拟器)
写在前面: 在写单元测试的时候有一个最重要的步骤就是Mock,我们通常会根据接口来Mock接口的实现,比如你要测试某个class中的某个方法,而这个方法又依赖了外部的一些接口的实现,从单元测试的角度来 ...
- 前端测试框架Jest系列教程 -- 简介
写在前面: 随着互联网日新月异的发展,用户对于页面的美观度,流畅度以及各方面的体验有了更高的要求,我们的网页不再是简单的承载文字,图片等简单的信息传递给用户,我们需要的是更加美观的页面展示,更快的浏览 ...
- 前端测试框架Jest系列教程 -- Asynchronous(测试异步代码)
写在前面: 在JavaScript代码中,异步运行是很常见的.当你有异步运行的代码时,Jest需要知道它测试的代码何时完成,然后才能继续进行另一个测试.Jest提供了几种方法来处理这个问题. 测试异步 ...
- 前端测试框架Jest系列教程 -- Matchers(匹配器)
写在前面: 匹配器(Matchers)是Jest中非常重要的一个概念,它可以提供很多种方式来让你去验证你所测试的返回值,本文重点介绍几种常用的Matcher,其他的可以通过官网api文档查看. 常用的 ...
- 前端测试框架Jest系列教程 -- Global Functions(全局函数)
写在前面: Jest中定义了很多全局性的Function供我们使用,我们不必再去引用别的包来去实现类似的功能,下面将列举Jest中实现的全局函数. Jest Global Functions afte ...
- 前端测试框架Jest系列教程 -- Expect(验证)
写在前面 在编写测试时,我们通常需要检查值是否满足某些条件,Jest中提供的expect允许你访问很多“Matchers”,这些“匹配器”允许您验证不同的东西. Expect 可以验证什么 Jest中 ...
- 前端测试框架jest 简介
转自: https://www.cnblogs.com/Wolfmanlq/p/8012847.html 作者:Ken Wang 出处:http://www.cnblogs.com/Wolfmanlq ...
- 前端测试框架 Jest
前端测试工具一览 前端测试工具也和前端的框架一样纷繁复杂,其中常见的测试工具,大致可分为测试框架.断言库.测试覆盖率工具等几类.在正式开始本文之前,我们先来大致了解下它们: 测试框架 测试框架的作用是 ...
随机推荐
- HBase Ganglia
- Leetcode961. N-Repeated Element in Size 2N Array重复N次的元素
在大小为 2N 的数组 A 中有 N+1 个不同的元素,其中有一个元素重复了 N 次. 返回重复了 N 次的那个元素. 示例 1: 输入:[1,2,3,3] 输出:3 示例 2: 输入:[2,1,2, ...
- CDA考试 ▏2017 CDA L1备考资源习题详解-统计基础部分
CDA考试 ▏2017 CDA L1备考资源习题详解-统计基础部分 <CDA LEVEL 1描述性分析典型例题讲解> 主讲人:CDA命题组委会 傅老师 ▏2017 CDA L1备考资源习题 ...
- netbeans 8.2 系统找不到指定的文件。
系统找不到指定的文件.Using CATALINA_BASE: "C:\Users\wishr\AppData\Roaming\NetBeans\8.2\apache-tomcat-8.0. ...
- 2019阿里云开年Hi购季大促主会场全攻略!
2019阿里云云上采购季活动已经于2月25日正式开启,从已开放的活动页面来看,活动分为三个阶段: 2月25日-3月04日的活动报名阶段.3月04日-3月16日的新购满返+5折抢购阶段.3月16日-3月 ...
- 字符界面总是显示 login incorrect
一般来说出现这样的提示,是因为登陆的密码错误,如果密码中有数字,最好用主键盘输入,用数字键盘会有错误.
- iview中table的render()函数
Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML.然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接 ...
- java并发系列(三)-----ReentrantLock(重入锁)功能详解和应用演示
1. ReentrantLock简介 jdk中独占锁的实现除了使用关键字synchronized外,还可以使用ReentrantLock.虽然在性能上ReentrantLock和synchronize ...
- mysql基础记录
1. 概念介绍 数据库:专门存储数据,存储数据的仓库,同时提供了对数据的操作方法,增删改查的方法 事务 事务:是作为一个单元的一组有序的数据库操作,如果组当中所有操作都成功,则事务执行成功,如果有一个 ...
- FATAL ERROR in native method: JDWP No transports initialized, jvmtiError=AGENT_ERROR_TRANSPORT_INIT(197)
自从继承了hibernate ,全都是些奇葩问题. 努力解决中,先发布,以备忘