前端开发工具集:单元测试(jest)
Posted 程序员小濠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了前端开发工具集:单元测试(jest)相关的知识,希望对你有一定的参考价值。
本文是前端开发工具系列之单元测试,主要会讨论利用常见的单元测试工具的原理和使用,保证前端开发时的代码正确性,该系列其他部分请参考这里。
本文后期会随着对自动化测试的探索不定时更新,最终会完成全套的自动化测试。
自动化测试分为三种类型
- unit test 单元测试是对一个模块、一个函数或者一个类来进行正确性检验的测试工作。
- integration test 集成测试是对多个模块作为一个整体进行测试。
- end-to-end 即E2E 端到端测试模拟真实用户的黑盒测试。
关于测试更多,可参考vue和react官方的描述
1 单元测试
单元测试和我们手动测试的逻辑是一样的,即执行某一个单元的逻辑,然后将执行结果和预期结果对比,如果一致则通过测试,否则失败。
一个测试框架通常包含两部分
- Test Runner 测试容器,自动执行内部的测试代码(包括断言库)
- Assertion Library 断言库,其中包含很多断言,即判断执行结果和预期是否一致。
比如
test('the best flavor is grapefruit', () => {//测试容器
expect(bestLaCroixFlavor()).toBe('grapefruit');//断言库
});
复制代码
执行结束后会自动将测试结果输出。
每个测试文件被称为test suite,每个具体的单元测试被称为test,即测试用例。
除了对常规的算法进行测试,还可以通过Snapshots测试ui。
前端测试框架这边推荐jest,也是react和vue官方推荐的测试工具
另外可参考
2 jest
jest和其他前端工具差不多,比如webpack,都可以在命令行或者npm script执行,可选的配置文件,各种钩子函数,用于各种具体实现的api。想来应该可以很容易上手,下面来了解一下。
2.1 基本使用
2.1.1 配置文件
配置文件可选,可通过jest --init
进行交互式创建,文件名默认为jest.config.js
或者通过--config
参数指定。
具体配置选项查看这里。
配置完了可以在npm scripts中添加"test": "jest",
,使用--watch
或--watchAll
可以在修改文件后自动测试。
2.1.2 代码组织
每次执行jest时,jest会根据testMatch和testRegex的配置会查找相关测试代码,我们这里选择建立一个名为__tests__
的文件夹,其中的.js, .jsx, .ts and .tsx文件就会被当作测试文件。
比如我们有一个待测试函数,路径为src/index.js
export const sum=(a,b)=>a+b
复制代码
测试文件__tests__/sum.test.js
,其中使用的一些jest方法为全局方法,因此不需要显式引入。
import {sum} from '../src/index'
test('adds 1 + 2 to equal 3', () => {
expect(sum(1, 2)).toBe(3);
});
复制代码
执行`yarn test
yarn run v1.17.3
$ jest
PASS __tests__3/sum.test.js
√ adds 1 + 2 to equal 3 (2 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.352 s, estimated 2 s
Ran all test suites.
Done in 2.39s.
复制代码
以上就是一个完整的单元测试,是我们测试系统的一个最小单元。
2.2 expect和mathers
当我们写一个具体的测试时,需要两部分进行对比,即使用expect将待测试结果与表示期望结果的matchers进行匹配。
最常用的expect语法是expect(value)
,后面添加.not
表示取反。
这里主要讲一下常用matchers,除了以下之外的场景可查看expect这里的文档。
其实matcher本身就是expect对象的一个方法。
- Common Matchers
最简单的方式是精确匹配,其中toBe
使用Object.is
,如果想检查对象可以使用toEqual
,后者递归处理。
test('object assignment', () => {
const data = {one: 1};
data['two'] = 2;
expect(data).toEqual({one: 1, two: 2});
});
复制代码
- Truthiness
这里的matchers用来检查各种和布尔有关的值
- toBeNull 匹配null
- toBeUndefined 匹配undefined
- toBeDefined 非undefined
- toBeTruthy if语句作为true处理的场景
- toBeFalsy 和上面相反
- Numbers
包括用于比较的matchers
test('two plus two', () => {
const value = 2 + 2;
expect(value).toBeGreaterThan(3);
expect(value).toBeGreaterThanOrEqual(3.5);
expect(value).toBeLessThan(5);
expect(value).toBeLessThanOrEqual(4.5);
// toBe and toEqual are equivalent for numbers
expect(value).toBe(4);
expect(value).toEqual(4);
});
复制代码
对于浮点类型的,由于误差,要使用
test('adding floating point numbers', () => {
const value = 0.1 + 0.2;
//expect(value).toBe(0.3); This won't work because of rounding error
expect(value).toBeCloseTo(0.3); // This works.
});
复制代码
- Strings
可以对字符串匹配正则
test('there is no I in team', () => {
expect('team').not.toMatch(/I/);
});
复制代码
- Arrays and iterables
使用toContain检查是否包含某元素
- Exceptions
使用toThrow
2.3 测试异步代码
对于异步执行的代码,jest需要知道合适结束,以便去执行下一个测试。
- Callbacks
对于以回调执行的异步,不能直接在回调中检查,因为jest执行结束当前代码即表示完成,而回调还未执行。
可在test的回调中添加参数done,当done被调用才算完成
test('the data is peanut butter', done => {
function callback(data) {
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
复制代码
- Promises
如果以promise处理异步,jest会等resolved后才处理,如果rejectd直接失败,注意此时要使用return,否则会在promise的数据返回之前结束。
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
复制代码
test('the fetch fails with an error', () => {
expect.assertions(1);
return fetchData().catch(e => expect(e).toMatch('error'));
});
复制代码
如果test的回调是async函数,也可以结合await处理异步。
2.4 钩子函数
钩子中的异步和其他
- 每个测试都执行
使用beforeEach 或 afterEach
beforeEach(() => {
initializeCityDatabase();
});
afterEach(() => {
clearCityDatabase();
});
test('city database has Vienna', () => {
expect(isCity('Vienna')).toBeTruthy();
});
test('city database has San Juan', () => {
expect(isCity('San Juan')).toBeTruthy();
});
复制代码
- 在本文件所有测试开始之前和全都结束后执行
使用beforeAll and afterAll
3. 作用域
除了上面的全局作用域,还可以使用describe语句创建块作用域,describe语句会比其他全局作用域的test更早执行
2.5 Mock函数
mock函数可以用于清除函数的原来实现、捕获函数调用、捕获构造函数的new调用等。
有两种mock的方法,要么使用test代码创建mock函数,要么使用manual mock覆盖模块依赖。
2.5.1 创建mock函数
可以使用jest.fn()
包装一个函数,然后在返回的包装后的函数的mock属性会包含它被调用的各种状态信息。
const mockCallback = jest.fn(x => 42 + x);
forEach([0, 1], mockCallback);
// The mock function is called twice
expect(mockCallback.mock.calls.length).toBe(2);
// The first argument of the first call to the function was 0
expect(mockCallback.mock.calls[0][0]).toBe(0);
// The first argument of the second call to the function was 1
expect(mockCallback.mock.calls[1][0]).toBe(1);
// The return value of the first call to the function was 42
expect(mockCallback.mock.results[0].value).toBe(42);
复制代码
设置包装后函数的返回值
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
复制代码
mock模块
用于mock模块时,比如我们要验证一个axios请求
// users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
复制代码
我们没必要真的调用axios,而是使用jest.mock()对该模块的实现进行改写,比如使用mockResolvedValue来mock resolve的值
// users.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
复制代码
mock全部实现
// foo.js
module.exports = function () {
// some implementation;
};
// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
foo.mockImplementation(() => 42);
foo();
// > 42
复制代码
自定义matcher
前面通过jest.fn()
返回的mockfunc可以进行自定义
2.5.2 Manual Mocks
主要用于模拟数据存取,比如读取本地文件。具体参考这里
2.6 Snapshot
快照用于判断ui是否有变化,比如用于测试react组件,这个方式不会渲染整个app而是会将react tree序列化,(话说对比不同不应该git干的事么)。
比如有一个组件
import React from 'react'
export const List=()=>{
return <span>5555</span>
}
复制代码
对应测试文件
import React from 'react';
import renderer from 'react-test-renderer';
import {List} from '../src/index';
it('renders correctly', () => {
const tree = renderer
.create(<List></List>)
.toJSON();
expect(tree).toMatchSnapshot();
});
复制代码
执行测试后会生成snap文件表示当前一个快照,下次执行时进行对比,如果不同则会报错。如果要更新快照要使用jest --updateSnapshot
2.7 dom操作
jest在node中polyfill了一套浏览器api,可以直接操作dom。
2.8 jest对象
jest对象也是在全局作用域,其中挂载了一些属性和方法,主要包括以下几类
- Mock Modules
- Mock functions
- Mock timers
- Misc
最后:【可能给予你帮助】然后下面分享一些我的自学资料,希望可以帮到大家。
这份资料整体是围绕着【软件测试】来进行整理的,主体内容包含:python自动化测试专属视频、Python自动化详细资料、全套面试题等知识内容。对于软件测试的的朋友来说应该是最全面和完整的备战仓库了,这个仓库也陪伴我走过了很多坎坷的路,希望也能帮助到你。
关注我的微信公众号:【 程序员小濠】免费获取~
加群:175317069,也可以获取,群里有测试大牛分享经验。
最后感谢相遇,感谢缘分,感谢支持,感谢选择,感谢信任。也祝大家可以顺利找到心仪的工作,成功转行软件测试工程师!拿下高薪!
如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一键三连哦!
以上是关于前端开发工具集:单元测试(jest)的主要内容,如果未能解决你的问题,请参考以下文章