Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit e66e4f6

Browse files
authored
Merge pull request #44 from suiboyu/suiboyu-sepc
Update spec.md
2 parents 256ef70 + b9ecde6 commit e66e4f6

File tree

1 file changed

+374
-0
lines changed

1 file changed

+374
-0
lines changed

‎docs/es6/spec.md

Lines changed: 374 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,374 @@
1+
对于基础类库来说,保证其质量和稳定性是最重要的。开源工具的架构设计中,lodash、vue、react、ant-design,无一例外其中都包含了大量的前端自动化测试内容,用来保证类库的稳定性。
2+
3+
再从研发的整个过程来看测试集成,**测试环节是保证持续集成和交付的关键。**
4+
5+
### 测试集成
6+
7+
借用经典的软件开发测试模型--V-Model模型来说明,对测试集成的分类可以分为单元测试、集成测试、冒烟测试、验收测试4个环节。通过CI/CD流水线,其中的一些测试方式可以集成到其中。
8+
9+
### 单元测试
10+
11+
单元测试(Unit Test),属于对编码实现细节的测试,把模块、组件或者函数作为一个单元编写测试用例,来对功能做验证。单测的用例代码和模块的源码放在一起,随着模块的编译构建一起执行。
12+
13+
前端开发中经常见到的 jest、mocha都属于单元测试库。
14+
15+
### 集成测试
16+
17+
集成测试(Integration Test),属于泛指系统的功能性测试,确保了系统的所有单元可以按照预期的功能运行。把相关的组件、模块集成在 Web页面中进行统一测试,通常和端到端的一起进行。一般在模块编译构建结束后执行。集成测试关注的是产品的单独一块功能,端到端测试关注的是产品功能之前的使用链路和数据流向。
18+
19+
集成测试也可以用单测库进行。前端常用的端到端类库有PhantomJS,casperJs,puppeteer。
20+
21+
### 系统测试
22+
23+
系统测试(System Test),属于对业务系统兼容性、性能、回归、可伸缩性、安全性等方面(web应用中可能还包括网络服务、IO、物理机CPU占用、内存消耗等等)的测试。通常需要配合使用一些GUI工具进行测试,一般在做系统功能测试时同步进行。
24+
25+
### 验收测试
26+
27+
验收测试(Acceptance Test),指从一个从用户的角度出发执行的测试,因此称为验收测试。这种测试在将软件交付给客户(即生产环境)之前执行。
28+
29+
### 单元测试
30+
31+
上面已经介绍了单元测试的定义,关于单元测试有几个概念需要了解下。**测试用例**是组成单元测试模块的最小结构,也就是说把测试用例放到一个测试模块里,就是一个完整的单元测试。**断言**是测试用例中最核心的部分,比如nodejs中的 assert 模块,如果当前程序的某种状态符合 assert 的期望此程序才能正常执行,否则直接退出应用。
32+
33+
断言是单元测试框架中核心的部分,断言失败会导致测试不通过,或报告错误信息。
34+
35+
对于常见的断言,举一些例子如下:
36+
37+
- 同等性断言 Equality Asserts
38+
- expect(sth).toEqual(value)
39+
- expect(sth).not.toEqual(value)
40+
- 比较性断言 Comparison Asserts
41+
- expect(sth).toBeGreaterThan(number)
42+
- expect(sth).toBeLessThanOrEqual(number)
43+
- 类型性断言 Type Asserts
44+
- expect(sth).toBeInstanceOf(Class)
45+
- 条件性测试 Condition Test
46+
- expect(sth).toBeTruthy()
47+
- expect(sth).toBeFalsy()
48+
- expect(sth).toBeDefined()
49+
50+
用一个函数来说明:
51+
52+
```js
53+
// 待测试函数 multiple
54+
55+
function multiple(a, b) {
56+
let result = 0;
57+
for (let i = 0; i < b; ++i)
58+
result += a;
59+
return result;
60+
}
61+
62+
// 断言
63+
const assert = require('assert');
64+
assert.equal(multiple(1, 2), 2);
65+
```
66+
67+
但是nodejs 自带的 assert 模块只能满足一些简单场景的需要,而且提供的错误信息提示不太友好,其次输出的内容是程序的错误报告,而不是一个单元测试报告,所以在做单元测时需要专业的断言库提供测试报告,这样才能看到有哪些断言通过没通过。
68+
69+
### 断言库
70+
71+
断言库主要提供上述断言的语义化方法,用于对参与测试的值做各种各样的判断。这些语义化方法会返回测试的结果,要么成功、要么失败。常见的断言库有 Should.js, Chai.js 等。
72+
73+
74+
75+
## 测试工具
76+
77+
首先要明确一点,所有的单元测试要在不同的环境下执行就要打不同环境对应的包,所以在搭建测试工具链时要确定自己运行在什么环境中,如果在 Node 中只需要加一层 babel 转换,如果是在真实浏览器中,则需要增加 webpack 处理步骤。
78+
79+
80+
81+
## mocha
82+
83+
mocha 是一个经典的测试框架,它提供了一个单元测试的骨架,可以将不同子功能分成多个文件进行测试,最后生成一份结构型的测试报告。karma是一个测试执行过程管理工具,可以watch文件更新。
84+
85+
Node 环境下测试 : mocha + chai + babel
86+
87+
浏览器环境测试 : karma + mocha + chai + webpack + babel + jsdom
88+
89+
但是mocha配置起来比较繁琐,还有一些额外的工具例如单元覆盖率(istanbul),函数模拟 (sinon.js)等辅助工具,选型的成本比较高。
90+
91+
92+
93+
## jasmine
94+
95+
jasmine 也是一个常用的测试框架,里面包含了 测试流程框架,断言函数,mock工具等测试中会遇到的工具。可以近似地看作 jasmine = mocha + chai + 辅助工具。
96+
97+
Node 环境下测试 : Jasmine + babel
98+
99+
浏览器环境测试 : karma + Jasmine + webpack + babel + jsdom
100+
101+
102+
103+
## jest
104+
105+
Jest 是 facebook 出的一个完整的单元测试技术方案,集 测试框架, 断言库, 启动器, 快照,沙箱,mock工具于一身,也是 React 官方使用的测试工具。Jest的优势是明显的:
106+
107+
速度快,具备监控模式,API 简单,易配置,隔离性好,Mock 丰富,多项目并行。
108+
109+
110+
111+
Node 环境下测试 : Jest + babel
112+
113+
浏览器环境测试 : Jest + babel + JSDOM(组件需要 [react-testing-library](https://github.com/kentcdodds/react-testing-library), [Enzyme](http://airbnb.io/enzyme/), [TestUtils](https://reactjs.org/docs/test-utils.html) 三选一) + webpack
114+
115+
### 安装
116+
117+
项目里安装 Jest 、babel
118+
119+
```bash
120+
npm install --save-dev jest babel-jest @babel/core @babel/preset-env @babel/preset-typescript @types/jest
121+
```
122+
123+
### 初始化配置
124+
125+
在根目录下生成 jest.config.js 的配置文件,scripts里添加的jest命令会在 jest.config.js 里找配置。
126+
127+
```bash
128+
jest --init
129+
```
130+
131+
配置 jest
132+
133+
```js
134+
// jest.config.js
135+
136+
module.exports = {
137+
verbose: true,
138+
roots: ['<rootDir>/packages'], // 文件入口
139+
moduleNameMapper: {
140+
'\\.(css|less|scss)$': 'identity-obj-proxy' // 使用 identity-obj-proxy mock CSS Modules
141+
},
142+
testRegex: '(/test/.*|\\.(test|spec))\\.(ts|tsx|js)$', // 匹配测试文件
143+
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx'],
144+
testPathIgnorePatterns: ['<rootDir>/node_modules/', '/node_modules/', '/lib/', '/demo/', '/dist/'],
145+
preset: 'ts-jest',
146+
testEnvironment: 'jsdom',
147+
transform: {
148+
'\\.[jt]sx?$': 'babel-jest', // 文件处理
149+
'^.+\\.svg$': 'jest-svg-transformer', // svg转换
150+
'^.+\\.(ts|tsx)$': 'ts-jest'
151+
},
152+
setupFiles: ['jest-canvas-mock'],
153+
transformIgnorePatterns: ['<rootDir>/node_modules/(?!lodash-es)'], // transform编译忽略哪些文件
154+
collectCoverage: true, // 开启收集Coverage(测试覆盖范围)
155+
coverageDirectory: '<rootDir>/coverage/', // 指定生成的coverage目录
156+
coveragePathIgnorePatterns: ['<rootDir>/coverage/'] //该路径下的测试,忽略测试覆盖率
157+
}
158+
```
159+
160+
babel配置:
161+
162+
```js
163+
{
164+
presets: [
165+
'@babel/preset-react',
166+
[
167+
'@babel/preset-env', {targets: {node: 'current'}}
168+
],
169+
'@babel/preset-typescript'
170+
],
171+
plugins: [
172+
[
173+
'import',
174+
{ libraryName: 'antd', libraryDirectory: 'es', style: true }
175+
],
176+
['@babel/plugin-transform-runtime'],
177+
['@babel/plugin-transform-modules-commonjs'] // jest不支持es模块,用babel处理
178+
]
179+
}
180+
```
181+
182+
### 基础语法
183+
184+
#### 匹配器 matchers
185+
186+
断言api。查看jest支持的所有断言api:https://www.jestjs.cn/docs/expect
187+
188+
```js
189+
// 测试相等
190+
test('two plus two is four', () => {
191+
// toBe 匹配器
192+
expect(2 + 2).toBe(4);
193+
});
194+
195+
// 测试对象相等 内容相等
196+
test('object assignment', () => {
197+
const data = {one: 1};
198+
data['two'] = 2;
199+
// toEqual 匹配器
200+
expect(data).toEqual({one: 1, two: 2});
201+
});
202+
```
203+
204+
#### 异步代码测试
205+
206+
测试异步代码的执行结果
207+
208+
```js
209+
test('the data is peanut butter', () => {
210+
return fetchData().then(data => {
211+
expect(data).toBe('peanut butter');
212+
});
213+
});
214+
215+
test('the data is peanut butter', async () => {
216+
await expect(fetchData()).resolves.toBe('peanut butter');
217+
});
218+
219+
220+
// 测试reject
221+
test('the fetch fails with an error', () => {
222+
// expect至少执行一次,如果不加则不会执行 catch
223+
expect.assertions(1);
224+
return fetchData().catch(e => expect(e).toMatch('error'));
225+
});
226+
```
227+
228+
#### 钩子函数
229+
230+
在测试运行前后进行一些设置,例如初始化测试代码等,保证每个测试的输入都是一致的。
231+
232+
```js
233+
export default class Counter {
234+
constructor (){
235+
this.number = 0;
236+
}
237+
addOne(){
238+
this.number += 1
239+
}
240+
minusOne() {
241+
this.number -= 1
242+
}
243+
}
244+
245+
test('加一', function() {
246+
counter.addOne();
247+
expert(counter.number).toBe(1)
248+
})
249+
250+
test('减一', function() {
251+
counter.minusOne();
252+
expert(counter.number).toBe(0)
253+
})
254+
255+
256+
/* ----------------------------------- */
257+
258+
259+
let counter = null;
260+
// 所有测试用例生成单独的 counter对象 保证独立
261+
beforeEach(() => {
262+
counter = new Counter();
263+
})
264+
265+
test('加一', function() {
266+
counter.addOne();
267+
expert(counter.number).toBe(1)
268+
})
269+
270+
test('减一', function() {
271+
counter.minusOne();
272+
expert(counter.number).toBe(-1)
273+
})
274+
```
275+
276+
#### mock
277+
278+
函数执行mock。对函数是否被调用,调用结果是否正常做验证。
279+
280+
```js
281+
const mockCallback = jest.fn(x => 42 + x);
282+
forEach([0, 1], mockCallback);
283+
284+
// 函数被调用两次
285+
expect(mockCallback.mock.calls.length).toBe(2);
286+
287+
// 函数第一次被调用时,第一个参数是0
288+
expect(mockCallback.mock.calls[0][0]).toBe(0);
289+
290+
// 函数第二次被调用时,第一个参数是0
291+
expect(mockCallback.mock.calls[1][0]).toBe(1);
292+
293+
// 函数第一次被调用时,返回值是42
294+
expect(mockCallback.mock.results[0].value).toBe(42);
295+
```
296+
297+
#### snapshot 快照测试
298+
299+
用来测试配置文件或组件每次渲染是否一致。例如测试输入 schema form 配置功能,获取到输出的 data 快照是否一致。如果不一致,说明本次代码修改导致 data 的输出发生变化。
300+
301+
```js
302+
it('校验配置项是否正确', () => {
303+
const user = {
304+
createdAt: new Date(),
305+
id: Math.floor(Math.random() * 20),
306+
name: 'LeBron James',
307+
};
308+
309+
expect(user).toMatchSnapshot({
310+
createdAt: expect.any(Date),
311+
id: expect.any(Number),
312+
});
313+
});
314+
315+
// Snapshot
316+
exports[`will check the matchers and pass 1`] = `
317+
Object {
318+
"createdAt": Any<Date>,
319+
"id": Any<Number>,
320+
"name": "LeBron James",
321+
}
322+
`;
323+
```
324+
325+
初次运行,生成 .snap 文件。第二次运行会校验快照是否一致。
326+
327+
#### dom 测试
328+
329+
jest 测试dom操作,内置类似jsdom的能力。
330+
331+
```js
332+
// displayUser.js
333+
const $ = require('jquery');
334+
const fetchCurrentUser = require('./fetchCurrentUser.js');
335+
336+
$('#button').click(() => {
337+
fetchCurrentUser(user => {
338+
const loggedText = 'Logged ' + (user.loggedIn ? 'In' : 'Out');
339+
$('#username').text(user.fullName + ' - ' + loggedText);
340+
});
341+
});
342+
343+
344+
// __tests__/displayUser-test.js
345+
jest.mock('../fetchCurrentUser');
346+
347+
test('验证用户登录', () => {
348+
// 初始化dom
349+
document.body.innerHTML =
350+
'<div>' +
351+
' <span id="username" />' +
352+
' <button id="button" />' +
353+
'</div>';
354+
355+
require('../displayUser');
356+
357+
const $ = require('jquery');
358+
const fetchCurrentUser = require('../fetchCurrentUser');
359+
360+
// mock 函数返回
361+
fetchCurrentUser.mockImplementation(cb => {
362+
cb({
363+
fullName: 'Johnny Cash',
364+
loggedIn: true,
365+
});
366+
});
367+
368+
// 执行按钮点击事件
369+
$('#button').click();
370+
371+
expect(fetchCurrentUser).toBeCalled();
372+
expect($('#username').text()).toEqual('Johnny Cash - Logged In');
373+
});
374+
```

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /