对于新项目,在设计的时候就可以考虑尽可能的做单测;
对于已经存在多年的存量代码,而且变动也不大,我认为不需要补单测了,理由:已经存在那么多年,经过了N多专业的测试以及外网用户的测试,该发现的问题,都已经发现,不能发现的问题,这样的场景几年都不遇到,也没必要花费代价去补一个可能发生的问题;
对于迭代,新功能代码,可以尽量做单测(当然这里也要考虑实际情况,既然是迭代项目,如果老的代码结构设计不是太好,或者经过多人修改之后,代码存在严重耦合,而新的代码又无法避免的跟老的代码耦合在一起,这种情况,确实也不好做单测);
对于前端,逻辑层、底层库等,这一类变动一般不会太大,尽量都做单元测试;
对于前端,偏向UI的或者跟UI耦合重、变动频繁、兼容性多的,建议不做;
目前在项目中,做单测起步是由测试来做,包括了:调研、框架选择、环境搭建、编写部分单测、效果展示、单测宣导(主要是给开发同学来),逐步推进;最终,我们是希望由开发来编写单测用例。
灵活:由于Mocha只是提供了简单的测试结构,所以像断言以及mock并没有集成在框架中,但可以自行选择工具,一般会选择chai,即Mocha+chai的用法会比较多;Jest则不要额外的工具或者插件。
社区成熟,即:可查资料会比较丰富;Jest基本上只能在官网上查资料。如果你碰到问题,去百度,你会发现大部分的例子都是重复且照抄官网,没什么实际参考性。
Mocha需要较多配置,这里也是不太方便的地方,如果新手的话,会相对不太容易;相比Jest,Jest安装好之后,不用配置也可以直接使用;
用例代码写法以及风格基本上一致,可自动识别js环境通用标识,书写规则简单,编写起来容易;
Jest集成了覆盖率的功能,执行时,可直接输出覆盖率,不需要额外的工具或者插件,很方便;另外,还支持将结果写入指定的文件中,方便后续对结果展示处理以及扩展。
基于上述,对于新手而言,Jest是很好的工具,功能强大且容易上手;另外,跟开发同学沟通的时候,他们也倾向于Jest,这样的话,后续再给开发推广的时候,也会容易一些。
对于被测方法中如果调用到了其他的模块或者方法,那么我们可以使用mock功能来替代被调用函数的执行,这样既可以提高效率(不会执行被调用的函数),对数据的构造也更加灵活;
Mock分为手动mock和自动mock,自动mock需要在配置文件去配置,或者在被测试文件开头调用方法来实现;
Jest中支持三类mock:mock方法、mock模块、mock计时器。
场景1:Mock方法,目前写用例用到最多的就是将被测函数中调用的函数给一个返回值,如下图:isIOS函数直接返回true;Util.isIOS = Jest.fn(),表示将isIOS方法定义为一个mock方法,mockReturnValueOnce表示给mock方法一个返回值。
这里是其中一种写法,只针对isIOS函数进行mock;
场景2:其实也可以这样写,先mock Util,再给Util类下的方法返回值,这种是直接先将整个Util mock掉,这里跟模块的mock写法一样。
const mocktest = Jest.mock(‘Util’)
isIOS.mockReturnValueOnce(xx)
检查回调函数是否被执行,或者需要创建一个函数来传递给被测试函数时,此时我们可以用到定义一个mock函数(见场景3的图),然后将该函数作为参数传递给被测方法作为参数,然后检查被测试函数执行的结果,以及回调函数也就是我们自定义的mock函数,如:
//被测对象;
function test(a, callback){
console.log(’action‘);
callback(a);
}
//定义一个mock函数:
var funcTestMock = jest.fn((x) => {return x+1});
//将mock函数传给被测对象
test(5, funcTestMock);
funcTestMock调用jest对象,判断mock函数是否被执行,返回true或者false;
jest.enableAutomock();//启用自动mock
//jest.disableAutomock(); //关闭自动mock
import utils from '../utils';
test('test automock', () => {
expect(utils.xx._isMockFunction).toBeTruthy();
});
更多mock用法请参考官网:
https://jestjs.io/docs/zh-Hans/mock-function-api#mockfnmockreturnvaluevalue
创建一个JSDOM对象并在其中写入对应的数据,然后通过浏览器的方法去查找数据;
const dom = new JSDOM(`Hello world
`);
console.log(dom.window.document.querySelector("p").textContent);
主要是设置像userAgent\href\location.localStorage等这些数据,因为这些数据会依赖当前脚本的执行环境,无法拿到我们期望设定的数据;直接在jsdom对象中设置属性值
测试代码:
自定义的数据,指的是业务js方法执行之后,在浏览器中生成的一些业务数据,我们在测试被测对象,需要拿到指定的数据时,此时我们就需要去构造这些数据了,比如,如下方法中就会涉及到从浏览器中取业务数据:
被测试代码:
这种情况下,如果直接在jest框架中执行window.qv.zero.Idip或者window.mqq是无法拿到数据,且会报错,会报window对象下没有qv或者mqq这个属性;此时我就需要在浏览器模拟业务js执行来伪造一份数据,如下图:
测试代码:
此时,我们再去取数据window.qv.zero.Idip.xx.wxappid时,会取到数据’wx000’,先引用jsdom\vm(脚本虚拟执行),然后定义一个虚拟脚本对象,最后在dom中执行该js,将数据加载到dom对象中;
注意:在使用的时候,有个很关键的点,就是需要将window全局对象删除之后,重新定义一下,否则在使用的时候,还是会使用jsdom提供的全局window对象,使用完之后,记得删除,否则会影响同一个测试套中的其他用例的执行结果。
// 设置期望的window对象
delete global.window
const dom = (new JSDOM('', { runScripts: 'outside-only' }))
const s = new Script(`if (!this.qv) {
this.qv = { zero: { Idip: { ${page.game}: { wxappid: 'wx1002000491' } } } }
}`)
dom.runVMScript(s)
global.window = dom.window
更多jsdom用法,请参考:
http://npm.taobao.org/package/jsdom
Jest中自带了丰富的断言方法,基本上能满足我们所有的使用场景:
判断两个值是否相等,可以使用toBe()、toEqual();这两个断言也是用的最多的;但是这两个断言方法有些区别,刚开始用的时候,容易搞错:
toEqual()比较的是两个值是否相等;
toBe()不仅比较值,而且还会比较属性;
所以,我们在比较number\bool\string时,toBe和toEqual都可以使用;如果比较的是对象,而你只想对比它的值,建议使用toEqual();这两个的区别类似于比较运算符中的==、===的区别。
判断预期值和实际值相反,则使用not:
expect(false).not.toBeTruthy()
expect(xx).not.toBeUndefined()
toBeNull 只匹配null
toBeTruthy 匹配任何为真的语句
toBeFalsy 匹配任何为假的语句
toBeDefined 与undefined相反
toBeUndefined 只匹配undefined
expect(xx).toBeUnDefined()
toMatchObject(object) 判断对象
toHaveProperty(keypath, value) 判断指定path下是否有这个属性以及值
resolves 和 .rejects - 用来测试 promise
当然,你如果了解promise的用法,也可以使用其他办法来检查,比如:
检查红框中的返回值,返回的是一个promise对象
用例:
toHaveBeenCalled() - 用来判断一个函数是否被调用过,这个相当有用,在很多被测试的函数中,也许并没有返回值,但是会有一些回调函数作为参数传递给被测试函数,此时我们可以检查该回调函数是否被执行以及检查回调函数本身返回的数据;或者mock掉某个被调用函数之后,为了检查是否走入到该分支,也可以通过该mock函数是否被执行来检查测试。
toHaveBeenCalledTimes(number) - 判断函数被调用过几次
toContain() 判断实际值是否包含预期值
注:其他更多用法,可以参考官网~
Jest框架集成了覆盖率工具,执行的时候只需要加参数—coverage即可,如图:
Jest –coverage ./ 会自动寻找当前目录下文件名带test的文件来执行
注意:这里展现的覆盖率,会把涉及到的文件都算进去,比如:被测试文件调用了abc三个文件,那abc这三个文件的覆盖率都会呈现出来,但其实我们关注的可能只是被测试文件这一个。
这个对于我们以后去做自定义结果展示的时候,会非常有用,可以把里面无用的信息剔除掉,只展示需要的信息即可;比如:你的被测试文件中调用了其他文件中的方法或者类,此时按照框架给的图标结果,他会将被测方法中调用的文件代码覆盖数据也会展示出来,但这个文件并不是我们想展示的。执行命令为:
jest --json --outputFile="./test.txt" 以json的格式输出并保存为test.txt文件中。
环境搭建比较简单,只需要简单的几步就可以搞定;
这个根据直接喜好、习惯来安装就可以了,目前我这边使用的是webStorm, 还蛮好用。
本地执行js代码时,需要依赖node环境;下载完之后,直接安装即可。
安装好了之后,设置好环境变量,然后可以在命令窗输入node测试一下:
下载地址:https://nodejs.org/en/download/
Npm是一个包管理工具,方便下载一些相关的依赖;
我们在安装node的时候,npm也被装上了:可以在命令窗直接输入npm -v测试一下:
可以使用npm来安装:
npm i jest -D 安装到本地
npm i jest -g 安装到全局
npm install --save-dev jest 开发依赖模式安装,需要到项目目录下之后,执行命令
如果是成功安装到本地,则在本地会有一个node_modules的文件夹:
本地安装的情况下, Jest命令也只能在当前目录中执行,在其他地方执行会报错。
安装好之后,可以直接执行jest命令, 如图:会执行URL目录下所有带test文件名中的用例,这里会自动采用正则去匹配。
这里不配置也是可以直接跑起来的,如果需要一些定制的内容,则可以去配置文件中修改或者增加配置;可修改项目根目录下的package.json
可根据实际需要来配置:
详细可参考官网:https://jestjs.io/docs/zh-Hans/configuration
这里用例编写比较简单,站在不是编写被测试代码的一方,只要了解清楚需求实现,以及详细逻辑实现细节,读懂被测试代码,找出你要检查的点,基本上用例都很简单,前提当然是代码可测;目前我的写法是:一个方法对应一个describe,it方法中的是用例的具体实现;一个describe对应多个用例(it方法),如果被测文件中有多个方法,则用例文件对应多个describe;describe和it以及expect是jest框架中默认的全局变量(https://jestjs.io/docs/zh-Hans/api#describename-fn)。如果没有配置,可能会报语法错误,但是不影响执行,消除错误,可以在文件头加上:/* global describe it expect */,或者在eslint配置文件中进行配置。
/* global describe it expect */
describe("test func xx", ()=>{
it("case1", ()=>{
...
});
it("case2", ()=>{
...
});
it("case3", ()=>{
...
});
});
用例目录和被测试文件目录结构保持一致,且子目录名一样,用例文件中名字也基本一样,多了一个test,比如:被测试文件名为:index.js,用例名则为:index.test.js;当然这里你也可以使用其他规则去定义你的测试用例目录结构,只要方便查找查看就行。
实际项目中节奏:由于代码不多,基本上所有测试代码都由测试完成;
(1)底层实现,全部由测试来编写,和开发同步在分支上进行,进度会略慢,有问题的会反馈给开发,基本上手工测试验证完,用例可以开发并调试完成80%左右;
(2)代码上传周期:开发代码上传到分支,并非每天上传,这个需要跟开发沟通好,比如每天下班前提交一次代码。
(3)用例完成之后,以后每次开发再次改动代码必须执行完用例并且通过才能进行合入主干的操作。
可根据时间项目以及要求,动态调整节奏;
本文参考:
https://jestjs.io/docs