测试框架jest
- 开箱即用:基本不需要额外的配置即可使用
- 功能强大:自带断言、测试覆盖率工具,支持Mock、Snapshot、异步测试等
- 应用广泛:已经成为了vue cli和create-react-app默认集成的测试框架
- 文档丰富:jest DOC
test suit / test case
Test Case:测试用例,表示对一个功能点的测试
Test Suit:测试套件,表示一组相关用例的组合
const myBeverage = {
delicious: true,
sour: false,
}
// test unit
describe( 'my beverage',() => {
// test case
test('is delicious', () => {
expect(myBeverage.delicious).toBeTruthy();
})
// test case
test('is not sour', () => {
expect(myBeverage.sour).toBeFalsy();
})
})
断言:
在程序设计中,断言是一种一阶逻辑,目的是表示与验证开发者预期的结果。
当程序运行到断言的位置时,对应的断言应该为真。若断言为假时,程序会中止并给出错误消息
JS中的断言:
- 浏览器: console.assert
- Node: assert模块
- 第三方自定义: 例如Vuex
function assert (condition, msg) {
if (!condition) {
throw new Error(`[Vuex] ${msg}`)
}
}
Jest中的断言
Jest中内置了很多断言方法,称作“Matcher”
Matcher的使用
test('object assignment!',() => {
const data = {one: 1};
data[ 'two'] = 2;
expect(data).toEqual({one: 1, two: 2});
})
Jest中的Matchers,包含各种数据类型的各种判断方法,这里只列举常见的,详细文档见 jest macher:
test('null',() =>{
const n = null;
expect(n).toBeNull();
expect(n).toBeDefined();
expect(n).not.toBeUndefined();
expect(n).not.toBeTruthy();
expect(n).toBeFalsy();
})
test( 'two plus two', () => {
const value = 2 + 2:
expect(value).toBeGreaterThan(3);
expect(value ).toBeGreaterThanOrEqual(3.5);
expect(value ).toBeLessThan(5);
expect(value ).toBeLessThan0rEqual(4.5);
expect(value).toEqual(4);
})
异步测试:
在JavaScript中执行异步代码是很常见的。 当你有以异步方式运行的代码时,Jest 需要知道当前它测试的代码是否已完成,然后它可以转移到另一个测试。 Jest有若干方法处理这种情况。
异步测试 - Promise
用例中返回一个Promise,则Jest会等待Promise的resove状态 如果 Promise 的状态变为 rejected, 测试将会失败
let fetchData = Promise.resolve("peanut butter")
test('the data is peanut butter', () => {
return fetchData().then(data => {
expect(data).toBe('peanut butter');
});
});
异步测试 - Async/Await
let fetchData = Promise.resolve("peanut butter")
test('the data is peanut butter', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
异步测试 - callback
test('the data is peanut butter', done => {
function callback(error, data) {
if (error) {
done(error);
return;
}
try {
expect(data).toBe('peanut butter');
done();
} catch (error) {
done(error);
}
}
fetchData(callback);
});
辅助函数
写测试的时候你经常需要在运行测试前做一些准备工作,和在运行测试后进行一些收尾工作。 Jest 提供辅助函数来处理这个问题。
- beforeEach
- afterEach
- beforeAll
- afterAll
/在该文件中的所有test运行之前,都会执行
beforeEach(() => {
return initializeCityData()
})
test('city database has Vienna',() => {
expect( isCity('Vienna' )).toBeTruthy();
})
describe('matching cities to foods',() => {
// 只会在这个test suit的每个test前运行
beforeEach(() => {
return initializeFoodDatabase( );
})
test('Vienna <3 sausage',() => {
expect(isValidCityFoodPair('Vienna','Wiener Schnitzel')toBe(true );
})
});
Mock
Mock是一种覆盖原有函数、类的实际实现,来检测其调用情况的一种测试方法
const forEach = require('./forEach');
const mockCallback = jest.fn(x => 42 + x);
test('forEach mock function', () => {
forEach([0, 1], mockCallback);
// mockCallback被调用2次
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);
//mockCallback的第一次调用的结果应该是42
expect(mockCallback.mock.results[0].value).toBe(42);
});
Mock一个模块
//users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
//users.test.js
import axios from 'axios';
import Users from './users';
//用 jest.mock(...) 函数自动模拟 axios 模块
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
//模拟模块时为 .get 提供了一个 mockResolvedValue ,它会返回假数据用于测试
axios.get.mockResolvedValue(resp);
return Users.all().then(data => expect(data).toEqual(users));
});
Snapshot
Snapshot也称快照,是一种非常强大的测试工具,一般用于UI组件的测试
实现是通过将某次的执行结果拍照做为后续ui比对的参考,所以组件UI变更后应该更新组件的快照
import renderer from 'react-test-renderer';
import Link from '../Link';
it('renders correctly', () => {
const tree = renderer
.create(<Link page="http://www.facebook.com">Facebook</Link>)
.toJSON();
expect(tree).toMatchSnapshot();
});
//生成快照文件内容
exports[`renders correctly 1`] = `
<a
className="normal"
href="http://www.facebook.com"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Facebook
</a>
`;
测试覆盖率
测试覆盖率是用于衡量一个工程测试代码完整程度的指标,Jest内置了Istanbul模块,可以从以下 4个维度统计测试覆盖率:
- Statements 语句覆盖率,即所有语句的执行率
- Branches 分支覆盖率,即所有代码分支如if、三目运算的执行率
- Functions 函数覆盖率,即所有函数的被调用率
- Lines 行覆盖率 即所有有效代码行的执行率,和语句类似,但是计算方式略有差别
lstanbul的原理
lstanbul在代码被执行之前,拦截了模块加载器,为其中的每一个逻辑分支、函数等添加了计数器,从而得到覆盖率结果。
测试报告
一次自动化测试的详情和最终结果,应该由一张 测试报告描述
测试分类
单元测试
单元测试应该只测试一个独立单元的工作
前端单元测试,往往是以组件、util为粒度
它的作用是提升软件质量,保证重构代码的安全性
详见 单元测试
E2E测试
End to End,即“端到端”
模仿用户,从某个入口开始,逐步执行操作,直到完成某项工作
关注一个完整的操作逻辑链是否能够完成
详见 E2E测试
最佳实践
过多和过少的测试都不好:平衡开发效率和代码质量是一个值得长期讨论的问题
用例不应和内部实现耦合:防止影响重构、造成测试用例“过拟合”
尽可能靠近边界条件:边界条件是最容易出现bug的地方
慎重使用 Mock 功能:Mock会造成测试偏离真实环境,这是我们应该尽量去避免的
编写Testable的代码:单一职责、无状态