0%

jest入门

jest是什么

进入正题前先讲一个老笑话:

一群软件项目经理参加一个培训。培训老师提了个问题:“假设今天你手头的项目完成了,恰好这个项目的内容是制造一架飞机,那么,你愿意参加首飞吗?愿意的请举手。”
台下只有两位举起了手,培训老师把那两位项目经理请上了讲台,然后问他们为什么敢于举手。
第一位说:“我们的管理很严格,规范也很全面,我相信我的团队100%完成了任务,我有信心”。全场都向他投去了钦佩羡慕的目光。
轮到第二位了,他说:“和他相反,我坚信我们的飞机根本飞不起来,所以我能很放心的参加首飞。”

尽管这是一个笑话,但也说明开发完成完全不能等同于交付生产。其中测试是必不可少的环节。

说到软件测试,往往想到的是软件测试人员在软件开发完成后,对软件产品的测试。其实在开发阶段,开发人员就可以进行单元测试和集成测试了。

单元测试和集成测试,可以把代码缺陷消灭在萌芽阶段,避免存在问题的代码进入后面的环节,降低成本。

jest就是可以用于这两方面的JavaScript测试框架,Facebook出品。

jest拥有比较全面的断言库, 支持同步异步代码测试,能提供测试覆盖率报告,对于某些不便直接测试的功能,还可以提供Mock支持。

jset如何使用

因为jset是一个npm包,这里按照通常情形:在自己的npm项目中使用jset。其他使用情形不讨论。

安装

使用npm或者yarn安装jset包即可。

1
yarn add --dev jest

或者

1
npm install --save-dev jest

官方推荐使用yarn

配置

定制生成jest配置文件

运行如下命令:

1
npx jest --init

命令运行后,会有一系列问答,根据需求设置就好。完成后会在项目目录中生成名为jest.conifg.js的文件,它是一个node模块,定义了jest的配置信息,具体内容在文件中有注释,可以查看。

配置自定义npm命令

为了方便调用jest命令,在package.json文件中添加如下的命令脚本:

1
2
3
4
"scripts": {
"test": "jest",
"coverage":"jest --coverage"
}

配置完成后,运行npm run test就可以进行jset测试,运行 npm run converage就可以进行jset测试同时生成测试报告。

如果想一边写测试代码,一边看结果,不妨做如下设置:

1
2
"test": "jest ----watchAll",
...

运行这个命令后jest测试完成后,不会返回终端,而是等待测试文件的修改,一旦发现测试文件修改,就立即执行测试。

配置支持ES6语法

jset并不天然支持ES6,所以需要使用babel进行转换,详见文档。其中也能找到支持TypeScript的配置方法。

使用

根据官方文档,可以把jest和webpack结合使用,作为入门文章这里只讲使用前面配置好的npm run testnpm run converage命令执行测试代码的情形。

jest测试代码编写

测试文件命名

jest测试代码也是js文件,只需要以xxx.test.js这样的格式命名即可。不是以此格式命名的文件会被忽略。

相关语句语法

  • expect语句能够将值和匹配器做比较,从而测试程序是否符合预期;

  • test(别名是it)语句包裹一个测试;

  • describe语句包裹一系列相关测试;

测试文件组织

  • 一个文件可以由一个或者多个describe语句组成,用来测试一组或者多组测试,如果已经通过测试文件分离了多组测试,那么可以没有describe语句;

  • 一个describe语句通常会包含一个或者多个test语句,describe还支持嵌套;

  • 一个test语句通常也会包含一个或者多个expect语句

测试文件样例

这样下来,一个测试文件大概长成这个样子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
describe('Test String', () => {

test('equal', () => {
expect('123).toBe('123')
})

test('contain', () => {
expect('123').toMatch('12')
})
})
describe('Test Number', () => {

test('equal', () => {
expect(123).toBe(123)
})

test('less', () => {
expect(123).toBeLessThan(124)
})

test('less or equal :1', () => {
expect(123).toBeLessThanOrEqual(123)
})

test('less or equal :2', () => {
expect(123).toBeLessThanOrEqual(124)
})

test('greate', () => {
expect(123).toBeGreaterThan(122)
})

test('great or equal :1', () => {
expect(123).toBeGreaterThanOrEqual(123)
})

test('less or equal :2', () => {
expect(123).toBeGreaterThanOrEqual(122)
})

// 解决计算精度不准的问题
test('plus', () => {
expect(123 + 0.3 + 0.1).toBeCloseTo(123.4)
})
})

PS:
以上例子中,expect语句中都直接给定了值,实际情况肯定不是如此,应该在测试文件中引用相关文件的相关方法,调用得到值,然后再和匹配器做比较。

匹配器

jest有一大堆让人眼花缭乱的匹配器,他们都在这里,有些让人生畏,但同时也是好事,找到合适的直接拿来用就行了。

常用匹配器

名称 等价方法 说明
toBe 对于基本类型,比较值(Object.is);对于引用类型比较是否为同一个引用
toEqual 对于引用类型,深度(递归)比较,基本类型比较值(Object.is)
toBeDefined something!=undefined 检查变量已经定义
toBeFalsy 检查内容是否为false, 0, '', null, undefined, 或者 NaN
toBeGreaterThan >
toBeGreaterThanOrEqual >=
toBeLessThan <
toBeLessThanOrEqual <=
toBeNull 检查预期值是null
toBeTruthy 和toBeFalsy相反
toBeUndefined 检查预期值是undefined
toBeNaN 检查预期值是NaN
toContain 内容是否在数组中(===);字符串是否包含在另一个字符串中
toContainEqual 检查内容是否在数组中,会进行递归深度比较
toMatch 检查字符串是否符合特定规则
toMatchObject 检查对象是否符合部分给定的特征
toStrictEqual 比较内容和特定的结构是否相符;可以比较对象数组、class实例和字面量对象,即使有相同的属性名称和值,也不能通过
toThrow(error?) 检查抛出了异常

异步测试

异步代码是JavaScript中常有的,异步测试和同步测试有很大的的区别,需要特别注意。

实现异步编程,通常有采用回调方法、Promiseasync/await三类,下面分别介绍这三种情况的测试。另外异步方法也可能抛出异常,我们也会介绍具体的测试方法。

测试回调方法

在测试的回调方法中一定要调用done方法。

done方法是jest自身提供的,需要在回调方法中的各种情况下都调用一次,这样在done之前的测试代码才会执行。比如如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
test('在回调方法中调用done,保证回调方法被执行,结果被验证', (done) => {
function calback(e, data) {
try {
console.log('callback测试2')
expect(data).toMatchObject({
code: '200'
})
done()
} catch (error) {
done(error)
}
}
doAsync(calback)
})

测试promise

记得要return

test中的测试方法一定要返回promise,否则测试代码无法执行。

1
2
3
4
5
6
7
test('promise 测试,正确例子,一定要返回', () => {
return getBjWeatherReport().then((response) => {
expect(response.data).toMatchObject({
code: '200'
})
})
})

采用expect.assertions() 测试异常

如果直接在primise对象的catch方法中测试,会漏掉测试方法正常执行没有被catch捕获的情况。所以需要在测试代码之前断言expect至少被执行一次:

1
2
3
4
5
6
7
test('promise测试,测试异常情况,正确例子',()=>{
// 断言 expcet至少要执行一次
expect.assertions(1)
return get404().catch(error=>{
expect(error.toString().indexOf('Error')>-1).toBe(true)
})
})

如果一个test只有一个测试内容,也可以使用 expect.hasAssertions()

.resolves/.rejects

可以使用.resolves/.rejects指定测试promise的返回结果。参见文档

Async/Await

测试代码需要声明为async方法,方法体内使用await等待返回。

1
2
3
4
5
6
test('async await',async ()=>{
let respones = await getBjWeatherReport()
expect(respones.data).toMatchObject({
code: '200'
})
})

也可以进一步和.resolves/.rejects组合。

钩子函数

  • beforeAll
  • afterAll
  • beforeEach
  • afterEach

分别在所欲测试开始前/后执行,每一项测试开始前/后执行。可以在这些钩子函数中做测试前的准备,测试后的善后。

测试分组和钩子函数

测试分组和钩子函数可以结合使用。

  • 在外面定义的钩子函数对内层有效
  • 层内定义的钩子函数支队本层以及子层有效。
  • 外层的before*钩子先执行
  • beforeAll先于beforeEach执行
  • 外层的after*钩子后执行
  • afterEach先于afterAll执行

开始:先执行外层beforeAll,后执行内层beforeAll ,直至没有内层

开始执行一条测试:先执行外层beforeEach,后执行内层beforeEach,直至没有内层;

一条测试执行完成:先执行内层afterEach,后执行外层afterEach,直至没有外层;

结束:先执行内层afterAll,后执行外层afterAll,直至没有外层

其他

only

可以给describetest添加only,这样没有添加only的测试组或者测试会被忽略。

skip

类似 only,但作用是忽略当前测试或者测试组。