测试分为e2e测试和单元测试和集成测试html
e2e:端到端的测试,主要是测业务,绝大部分状况是指在浏览器上对某一个网站的某一个功能进行操做。前端
单元测试工具:mache、ava、jest、jasmine等vue
断言库: shoud.js.chai.js 等node
测试覆盖率工具:istanbulreact
react 采用jest加enzyne的写法 e2e 测试pupertearandroid
vue 采用jest e2e 适应nightwatch 的方案正则表达式
单元测试npm
在计算机编程中,单元测试(英语:Unit Testing)又称为模块测试, 是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工做。程序单元是应用的最小可测试部件。在过程化编程中,一个单元就是单个程序、函数、过程等;对于面向对象编程,最小单元就是方法,包括基类(超类)、抽象类、或者派生类(子类)中的方法集成测试编程
集成测试,也叫组装测试或联合测试。在单元测试的基础上,将全部模块按照设计要求(如根据结构图)组装成为子系统或系统,进行集成测试。功能测试json
功能测试就是对产品的各功能进行验证,根据功能测试用例,逐项测试,检查产品是否达到用户要求的功能。
React & Redux 应用构建在三个基本的构建块上:actions、reducers 和 components。是独立测试它们(单元测试),仍是一块儿测试(集成测试)取决于你。集成测试会覆盖到整个功能,能够把它想成一个黑盒子,而单元测试专一于特定的构建块。从个人经验来看,集成测试很是适用于容易增加但相对简单的应用。另外一方面,单元测试更适用于逻辑复杂的应用。尽管大多数应用都适合第一种状况,但我将从单元测试开始更好地解释应用层。
vue中直接选就能够
在其余的项目中,直接测试就能够
npm install --save-dev jest
在package.json中添加
// 添加测试命令 { "scripts": { "test": "jest" } }
执行命令
npm test
Jest 的测试脚本名形如.test.js,不论 Jest 是全局运行仍是经过npm test运行,它都会执行当前目录下全部的.test.js 或 *.spec.js 文件、完成测试
一、相等匹配
expact(2 + 2) 将返回咱们指望的结果, toBe 就是一个matcher
test('two plus two is four', () => { expect(2 + 2).toBe(4); });
toBe 是测试具体的某一个值,若是须要测试对象,须要用到toEqual,toEqual是经过递归检查对象或数组的每一个字段。
test('object assignment', () => { const data = {one: 1}; data['two'] = 2; expect(data).toEqual({one: 1, two: 2}); });
二、真实性匹配,好比:对象是否为null,集合是否为空等等
在测试中,您有时须要区分undefined、null和false,但有时但愿以不一样的方式处理这些问题,Jest帮助你明确您想要什么。好比:
三、数字型匹配
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); });
于float类型的浮点数计算的时候,须要使用toBeCloseTo而不是 toEqual ,由于避免细微的四舍五入引发额外的问题
四、字符型匹配 toMatch 匹配规则,支持正则表达式匹配
test('there is no I in team', () => { expect('team').not.toMatch(/I/); }); test('but there is a "stop" in Christoph', () => { expect('Christoph').toMatch(/stop/); });
五、数组类型匹配 toContain 检查是否包含
const shoppingList = [ 'diapers', 'kleenex', 'trash bags', 'paper towels', 'beer', ]; test('the shopping list has beer on it', () => { expect(shoppingList).toContain('beer'); });
六、异常匹配 测试function是否会抛出特定的异常信息,能够用 toThrow 规则
function compileAndroidCode() { throw new ConfigError('you are using the wrong JDK'); } test('compiling android goes as expected', () => { expect(compileAndroidCode).toThrow(); expect(compileAndroidCode).toThrow(ConfigError); // You can also use the exact error message or a regexp expect(compileAndroidCode).toThrow('you are using the wrong JDK'); expect(compileAndroidCode).toThrow(/JDK/); });
一、回调函数
done() 被执行则意味着callback函数被调用
function fetchData(callback) { setTimeout(() => { callback('2') }, 2000) } test('data is 2', done => { function callback(data) { expect(data).toBe('2'); done(); } fetchData(callback) })
二、promise验证
assertions(1)表明的是在当前的测试中至少有一个断言是被调用的,不然断定为失败。
在Jest 20.0.0+ 的版本中你可使用 .resolves 匹配器在你的expect语句中,Jest将会等待一直到承诺被实现,若是承诺没有被实现,测试将自动失败。
若是你指望你的承诺是不被实现的,你可使用 .rejects ,它的原理和 .resolves相似
function fetchData() { return new Promise((resolve, reject) => { setTimeout(() => { resolve('2') }, 2000) }) } test('data is 2', () => { expect.assertions(1); return expect(fetchData()).resolves.toBe('2'); }) test('data is 2', () => { expect.assertions(1); return expect(fetchData()).rejects.toMatch('error'); });
三、使用 Async/Await
function fetchData(num) { return new Promise((resolve, reject) => { setTimeout(() => { if(num) { reject('error') } else { resolve('2') } }, 2000) }) } test('data is 2', () => { expect.assertions(1); return expect(fetchData()).resolves.toBe('2'); }) test('the data is 2', async () => { expect.assertions(1); const data = await fetchData(); expect(data).toBe('2'); }); test('the fetch fails with an error', async () => { expect.assertions(1); try { await fetchData(1); } catch (e) { expect(e).toMatch('error'); } });
固然你也能够将Async Await和 .resolves .rejects 结合起来(Jest 20.0.0+ 的版本)
test('the data is peanut butter', async () => { expect.assertions(1); await expect(fetchData()).resolves.toBe('peanut butter'); }); test('the fetch fails with an error', async () => { expect.assertions(1); await expect(fetchData()).rejects.toMatch('error'); });
在写单元测试的时候有一个最重要的步骤就是Mock,咱们一般会根据接口来Mock接口的实现,好比你要测试某个class中的某个方法,而这个方法又依赖了外部的一些接口的实现,从单元测试的角度来讲我只关心我测试的方法的内部逻辑,我并不关注与当前class自己依赖的实现,因此咱们一般会Mock掉依赖接口的返回,由于咱们的测试重点在于特定的方法,因此在Jest中一样提供了Mock的功能
Jest中有两种方式的Mock Function,一种是利用Jest提供的Mock Function建立,另一种是手动建立来覆写自己的依赖实现。
一、 jest.fn() 方式
every
function every(array, predicate) { let index = -1 const length = array == null ? 0 : array.length while (++index < length) { if (!predicate(array[index], index, array)) { return false } } return true } module.exports = every
foreach
function foreach(arr, fn) { for(let i = 0, len = arr.length; i < len; i++) { fn(arr[i]); } } module.exports = foreach;
const foreach = require('./foreach'); const every = require('./every'); describe('mock test', () => { it('test foreach use mock', () => { // 经过jest.fn() 生成一个mock函数 const fn = jest.fn(); foreach([1, 2, 3], fn); // 测试mock函数被调用了3次 expect(fn.mock.calls.length).toBe(3); // 测试第二次调用的函数第一个参数是3 expect(fn.mock.calls[2][0]).toBe(3); }) it('test every use mock return value', () => { const fn = jest.fn(); // 能够设置返回值 fn .mockReturnValueOnce(true) .mockReturnValueOnce(false); const res = every([1, 2, 3, 4], fn); expect(fn.mock.calls.length).toBe(2); expect(fn.mock.calls[1][1]).toBe(1); }) it('test every use mock mockImplementationOnce', () =>{ // 快速定义mock的函数体,方便测试 const fn = jest.fn((val, index) => { if(index == 2) { return false; } return true; }); const res = every([1, 2, 3, 4], fn); expect(fn.mock.calls.length).toBe(3); expect(fn.mock.calls[1][1]).toBe(1); }) })
二、手动
假如个人测试文件sum2.js
function sum2(a, b) { if (a > 10) return a * b; return a + b; }
export default sum2;
如今若是咱们要mock sum2.js 文件的话,须要在sum2.js 同级目录下新建文件夹__mock__,
而后在此文件下新建文件同名 sum2.js, 只是单纯的返回100
export default function sum2(a, b) { return 100; } 测试用例mock_file.test.js jest.mock('../src/sum2'); import sum2 from '../src/sum2'; it('test mock sum2', () => { // 由于此时访问的是__mock__文件夹下的sum2.js 因此测试经过 expect(sum2(1, 11111)).toBe(100); })
手动mock的好处是测试和模拟分离。能够很方便的修改测试用例。若是是复杂的mock建议使用手动新建文件方式
class Hook { constructor() { this.init(); } init() { this.a = 1; this.b = 1; } sum() { return this.a + this.b; } } describe('hook', () => { const hook = new Hook; // 每一个测试用例执行前都会还原数据,因此下面两个测试能够经过。 beforeEach( () => { hook.init(); }) test('test hook 1', () => { hook.a = 2; hook.b = 2; expect(hook.sum()).toBe(4); }) test('test hook 2', () => { expect(hook.sum()).toBe(2);// 测试经过 }) })
describe(name, fn)
describe(name, fn)建立一个块,在一个“测试套件”中,将几个相关的测试组合在一块儿
const myBeverage = { delicious: true, sour: false, }; describe('my beverage', () => { test('is delicious', () => { expect(myBeverage.delicious).toBeTruthy(); }); test('is not sour', () => { expect(myBeverage.sour).toBeFalsy(); }); });
这不是必需的——你能够直接在顶层编写测试块。可是,若是您但愿将测试组织成组,那么这就很方便了
describe.only(name, fn)
若是你只想运行一次模块测试的话你可使用 only
describe.only('my beverage', () => { test('is delicious', () => { expect(myBeverage.delicious).toBeTruthy(); }); test('is not sour', () => { expect(myBeverage.sour).toBeFalsy(); }); }); describe('my other beverage', () => { // ... will be skipped });
describe.skip(name, fn) describe 等价于 xdescribe
你可使用skip 跳过某一个测试
describe('my beverage', () => { test('is delicious', () => { expect(myBeverage.delicious).toBeTruthy(); }); test('is not sour', () => { expect(myBeverage.sour).toBeFalsy(); }); }); describe.skip('my other beverage', () => { // ... will be skipped });
使用跳过一般只是一种比较简单的替代方法,若是不想运行则能够暂时将大量的测试注释掉。
require.requireActual(moduleName)
返回实际的模块而不是模拟,绕过全部检查模块是否应该接收模拟实现。
require.requireMock(moduleName)
返回一个模拟模块,而不是实际的模块,绕过全部检查模块是否正常。
test(name, fn, timeout) 等价于 it(name, fn, timeout)
在测试文件中,您所须要的是运行测试的测试方法。例如,假设有一个函数inchesOfRain()应该是零。你的整个测试能够是:
test('did not rain', () => { expect(inchesOfRain()).toBe(0); });
第一个参数是测试名称;第二个参数是包含测试指望的函数。第三个参数(可选)是超时(以毫秒为单位),用于指定在停止前等待多长时间。注意:默认的超时是5秒。
注意:若是测试返回了一个promise,Jest会在测试完成以前等待promise。Jest还将等待,若是你为测试函数提供一个参数,一般称为done。当你想要测试回调时,这将很是方便。请参见如何在此测试异步代码。
test.only(name, fn, timeout)等同于 it.only(name, fn, timeout) or fit(name, fn, timeout)
test.skip(name, fn)等同于it.skip(name, fn) or xit(name, fn) or xtest(name, fn)
当您维护一个大型的代码库时,您可能有时会发现因为某种缘由而临时中断的测试。
若是您想跳过这个测试,可是您不想仅仅删除这个代码,您可使用skip指定一些测试来跳过。
test('it is raining', () => { expect(inchesOfRain()).toBeGreaterThan(0); }); test.skip('it is not snowing', () => { expect(inchesOfSnow()).toBe(0); });
只有“it is raining”测试运行,由于另外一个测试运行test . skip。 您能够简单地对测试进行注释,可是使用skip会更好一些,由于它将保持缩进和语法突出。
Jest 内置了测试覆盖率工具istanbul,要开启,能够直接在命令中添加 --coverage 参数,或者在 package.json 文件进行更详细的配置。
快照测试第一次运行的时候会将被测试ui组件在不一样状况下的渲染结果保存一份快照文件。后面每次再运行快照测试时,都会和第一次的比较。
import React from 'react'; export default class RC extends React.Component { render() { return ( <div>我是react组件 </div> ) } }
import React from 'react'; import renderer from 'react-test-renderer'; import RC from '../src/react-comp'; test('react-comp snapshot test', () => { const component = renderer.create(<RC />); // let tree = component.toJSON(); expect(tree).toMatchSnapshot(); }) test('react-comp snapshot test2', () => { const component = renderer.create(<RC />); let tree = component.toJSON(); expect(tree).toMatchSnapshot(); })
执行测试命令,会在test目录下生成一个__snapshots__目录,在此目录下会与一个文件叫snapshot.test.js.snap的快照文件
使用
npm install -g jest-codemods 而后jest-codemods
shallow() 渲染函数只渲染咱们专门测试的组件, 它不会渲染子元素。相反, 用mount()
一、使用 shallow
mport { shallow } from 'enzyme'; const wrapper = shallow(<MyComponent />);
咱们刚刚能够看到这个测试里用到shallow函数,它支持对DOM的进行结构和事件的响应,若是你对jQuery比较熟悉的话,那么你对它的语法也不会陌生。好比咱们测试里用到的find方法,你们常常用它来寻找一些DOM数组。
简单罗列下它所支持的方法:
二、彻底DOM渲染
import { mount } from 'enzyme'; const wrapper = mount(<MyComponent />);
彻底DOM渲染主要用于与DOM API进行交互以及须要完整生命周期的组件测试(i.e componentDidMoun)。彻底DOM渲染须要DOM 的 API 在全局做用域内。并且须要其运行在近似浏览器的环境里。若是你不想在浏览器里跑这些测试的话,强烈建议你使用mount,一个依赖于jsdom的类库,几乎等同于没有浏览器外壳的浏览器。它也支持了不少方法
三、静态渲染
静态渲染,enzyme还提供了静态渲染,将组件渲染成html,用于咱们分析html的结构。render相比前两种用法, 主要是在于更换了类库 Cheerio ,并且做者也相信在处理解析上会更好点。
import { render } from 'enzyme'; const wrapper = render(<MyComponent />);
若是咱们在开发过程当中就进行了测试(直接采用 TDD 开发模式、或者针对既有的模块写用例),
会有以下的好处:
固然,凡事都有两面性,好处虽然明显,却并非全部的项目都值得引入测试框架,毕竟维护测试用例也是须要成本的。对于一些需求频繁变动、复用性较低的内容,好比活动页面,让开发专门抽出人力来写测试用例确实得不偿失。而那些适合引入测试场景大概有这么几个:
由于多处复用,更要保障质量