javascript单元测试-为什么做、怎么做

js单元测试-为什么做、怎么做

  • 前言
    • (1)、为什么要做单测,怎么做
      • 做单测有没有必要?
      • 那么做单测的理由,同样很充分:
      • 那么对于单测如何做?是不是所有的代码都要去做,对于这个问题,我们不能一刀切,视项目情况而定:
  • 一、框架选择
    • 为什么会选择jest框架?
    • Mocha对比Jest有哪些不便与方便呢?
  • 二、Jest用法介绍**
    • 1、mock用法
      • (1)、mock方法--三种用法以及场景
      • (2)、mock模块
      • (3)、自动mock
    • 2、jsdom用法
      • (1)、创建DOM数据
      • (2)、创建浏览器环境数据
      • (3)、创建自定义数据
    • 3、断言
      • (1)、判断两个值是否相等
      • (2)、用来测试值相反
      • (3)、匹配布尔值
      • (4)、匹配对象
      • (5)、其他一些断言使用
    • 4、覆盖率
    • 5、将结果写入指定文件
  • 三、单元测试环境搭建
    • 1、ide安装
    • 2、node安装
    • 3、npm安装
    • 4、jest 安装
    • 5、jest配置
  • 四、用例代码结构以及用例文件结构
    • 1、用例代码结构
    • 2、用例文件结构
    • 3、用例开发节奏
  • 五、用例设计思路
  • 六、遇到的问题以及解决办法
    • 1、浏览器依赖
    • 2、不太好检查的点以及不太适合的做单测的点
    • 3、mock 某个方法会提示方法为只读


前言

(1)、为什么要做单测,怎么做

做单测有没有必要?

  • 我想很多的研发同学都是予以肯定的答复,但是在实际的项目中去做单测的少之又少,而给出的不做单测的理由也是各式各样,且看起来都有自己的道理,比如:项目上线时间紧,人手少;成本太高,浪费时间;测试不是我来做,是测试同学来做的;后面的集成测试和系统测试会搞定所有bug的;那么实际上做单测不仅不会影响开发效率、上线时间,而且还是很有效的测试手段。

那么做单测的理由,同样很充分:

  • 单元测试是最小颗粒度的测试,也是测试的第一个环节,也是最重要的一个环节;
  • 只有单元测试才可能让代码覆盖率达到100%;
  • 单元测试可以防止后期bug过多,而导致无法按时发布等失控情况的出现;
  • 80%的错误是在设计阶段引入,修正一个错误所需要的综合成本会随着研发周期逐步上升;
  • 单元测试可以发现集成测试和系统测试发现不了的问题;

那么对于单测如何做?是不是所有的代码都要去做,对于这个问题,我们不能一刀切,视项目情况而定:

  • 对于新项目,在设计的时候就可以考虑尽可能的做单测;

  • 对于已经存在多年的存量代码,而且变动也不大,我认为不需要补单测了,理由:已经存在那么多年,经过了N多专业的测试以及外网用户的测试,该发现的问题,都已经发现,不能发现的问题,这样的场景几年都不遇到,也没必要花费代价去补一个可能发生的问题;

  • 对于迭代,新功能代码,可以尽量做单测(当然这里也要考虑实际情况,既然是迭代项目,如果老的代码结构设计不是太好,或者经过多人修改之后,代码存在严重耦合,而新的代码又无法避免的跟老的代码耦合在一起,这种情况,确实也不好做单测);

  • 对于前端,逻辑层、底层库等,这一类变动一般不会太大,尽量都做单元测试;

  • 对于前端,偏向UI的或者跟UI耦合重、变动频繁、兼容性多的,建议不做;
    javascript单元测试-为什么做、怎么做_第1张图片
    目前在项目中,做单测起步是由测试来做,包括了:调研、框架选择、环境搭建、编写部分单测、效果展示、单测宣导(主要是给开发同学来),逐步推进;最终,我们是希望由开发来编写单测用例。


一、框架选择

为什么会选择jest框架?

  • 准备做之前,查了一下 js单元测试框架,发现框架很多:Mocha、Jasmine、Jest、Cucumber,相对比较流行的是Mocha和Jest,也了解了下各自的特点,发现Mocha上手起来会稍微复杂一点,Jest相对简单,但是Mocha的社区相对成熟,即能查的资料会相对多一些;

Mocha对比Jest有哪些不便与方便呢?

javascript单元测试-为什么做、怎么做_第2张图片

  • 灵活:由于Mocha只是提供了简单的测试结构,所以像断言以及mock并没有集成在框架中,但可以自行选择工具,一般会选择chai,即Mocha+chai的用法会比较多;Jest则不要额外的工具或者插件。

  • 社区成熟,即:可查资料会比较丰富;Jest基本上只能在官网上查资料。如果你碰到问题,去百度,你会发现大部分的例子都是重复且照抄官网,没什么实际参考性。

  • Mocha需要较多配置,这里也是不太方便的地方,如果新手的话,会相对不太容易;相比Jest,Jest安装好之后,不用配置也可以直接使用;

  • 用例代码写法以及风格基本上一致,可自动识别js环境通用标识,书写规则简单,编写起来容易;

  • Jest集成了覆盖率的功能,执行时,可直接输出覆盖率,不需要额外的工具或者插件,很方便;另外,还支持将结果写入指定的文件中,方便后续对结果展示处理以及扩展。

基于上述,对于新手而言,Jest是很好的工具,功能强大且容易上手;另外,跟开发同学沟通的时候,他们也倾向于Jest,这样的话,后续再给开发推广的时候,也会容易一些。


二、Jest用法介绍**

  • Jest是 Facebook 的一套开源的 JavaScript 测试框架, 它自动集成了断言、JSDom、覆盖率报告等开发者所需要的所有测试工具,是一款几乎零配置的测试框架。并且它对同样是 Facebook 的开源前端框架 React 的测试十分友好。

1、mock用法

  • 对于被测方法中如果调用到了其他的模块或者方法,那么我们可以使用mock功能来替代被调用函数的执行,这样既可以提高效率(不会执行被调用的函数),对数据的构造也更加灵活;

  • Mock分为手动mock和自动mock,自动mock需要在配置文件去配置,或者在被测试文件开头调用方法来实现;

  • Jest中支持三类mock:mock方法、mock模块、mock计时器。

(1)、mock方法–三种用法以及场景

  • 场景1:Mock方法,目前写用例用到最多的就是将被测函数中调用的函数给一个返回值,如下图:isIOS函数直接返回true;Util.isIOS = Jest.fn(),表示将isIOS方法定义为一个mock方法,mockReturnValueOnce表示给mock方法一个返回值。
    javascript单元测试-为什么做、怎么做_第3张图片
    这里是其中一种写法,只针对isIOS函数进行mock;

  • 场景2:其实也可以这样写,先mock Util,再给Util类下的方法返回值,这种是直接先将整个Util mock掉,这里跟模块的mock写法一样。

const mocktest = Jest.mock(‘Util’)
isIOS.mockReturnValueOnce(xx)
  • 场景3:还有一种写法是直接在jest的对象中进行返回,如下图:
    javascript单元测试-为什么做、怎么做_第4张图片

  • 检查回调函数是否被执行,或者需要创建一个函数来传递给被测试函数时,此时我们可以用到定义一个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;

(2)、mock模块

  • 什么情况下,我们需要去mock模块呢?比如我们调用了某个类或者文件下的大量的方法,如果一个个方法去mock的话,势必会比较麻烦,每次都要先定义一个mock对象,也会造成冗余的代码;此时,我们可以直接将整个类mock掉,然后直接操作该类下的方法,比如给方法直接返回一个值,如下图:我们直接将bindDataSource这个文件直接mock掉, 该文件下的所有方法默认变成了mock对象,此时,我们可以直接使用mock对象的方法来操作,比如:
    funcTest.mockReturnValue(‘xxx’);//给方法funcTest返回一个值xxx
    javascript单元测试-为什么做、怎么做_第5张图片
    反面例子,如图:
    javascript单元测试-为什么做、怎么做_第6张图片

(3)、自动mock

  • Jest支持在配置文件中配置是否自动mock,配置之后,所有测试对象被调用的函数或者模块,会自动被mock掉;当然如果不去配置,也可以在代码里面启用是否自动mock,如下:
jest.enableAutomock();//启用自动mock
//jest.disableAutomock(); //关闭自动mock
import utils from '../utils';
test('test automock', () => {
	expect(utils.xx._isMockFunction).toBeTruthy();
});
  • 如果是新手这里不建议使用自动mock,还是使用主动mock比较好,自动mock之后,有时,某些不需要mock时,也要说明某个文件或者模块不mock,具体使用jest.unmock(‘’);

更多mock用法请参考官网:
https://jestjs.io/docs/zh-Hans/mock-function-api#mockfnmockreturnvaluevalue

2、jsdom用法

  • 为什么要用到jsdom?因为测试的是前端js,js的代码往往会依赖浏览器,比如设置cookie,取cookie,取链接、取userAgent或者是取自定义的内容,比如:window.xx.yy.zz,此时我们该如何去设置这个环境以及这些数据呢?
  • Jest对象提供了一个类DOM的环境,也就是说,像我们的getElementById\CreateElement等浏览器自带的一些操作DOM的方法是可以在jest框架中运行的,但是有些是拿不到,且无法拿到我们想要的数据,比如cookies,比如userAgent,这些数据会依赖于你当时执行的机器环境,没办法按照你的预期来;此时我们就需要用到jsdom了;

(1)、创建DOM数据

创建一个JSDOM对象并在其中写入对应的数据,然后通过浏览器的方法去查找数据;

const dom = new JSDOM(`

Hello world

`
); console.log(dom.window.document.querySelector("p").textContent);

(2)、创建浏览器环境数据

主要是设置像userAgent\href\location.localStorage等这些数据,因为这些数据会依赖当前脚本的执行环境,无法拿到我们期望设定的数据;直接在jsdom对象中设置属性值
测试代码:
javascript单元测试-为什么做、怎么做_第7张图片

(3)、创建自定义数据

自定义的数据,指的是业务js方法执行之后,在浏览器中生成的一些业务数据,我们在测试被测对象,需要拿到指定的数据时,此时我们就需要去构造这些数据了,比如,如下方法中就会涉及到从浏览器中取业务数据:

被测试代码:
javascript单元测试-为什么做、怎么做_第8张图片
在这里插入图片描述
这种情况下,如果直接在jest框架中执行window.qv.zero.Idip或者window.mqq是无法拿到数据,且会报错,会报window对象下没有qv或者mqq这个属性;此时我就需要在浏览器模拟业务js执行来伪造一份数据,如下图:

测试代码:
javascript单元测试-为什么做、怎么做_第9张图片
此时,我们再去取数据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

3、断言

Jest中自带了丰富的断言方法,基本上能满足我们所有的使用场景:

(1)、判断两个值是否相等

判断两个值是否相等,可以使用toBe()、toEqual();这两个断言也是用的最多的;但是这两个断言方法有些区别,刚开始用的时候,容易搞错:

toEqual()比较的是两个值是否相等;

toBe()不仅比较值,而且还会比较属性;

所以,我们在比较number\bool\string时,toBe和toEqual都可以使用;如果比较的是对象,而你只想对比它的值,建议使用toEqual();这两个的区别类似于比较运算符中的==、===的区别。

例子:
javascript单元测试-为什么做、怎么做_第10张图片
javascript单元测试-为什么做、怎么做_第11张图片

(2)、用来测试值相反

判断预期值和实际值相反,则使用not:

expect(false).not.toBeTruthy()
expect(xx).not.toBeUndefined()

(3)、匹配布尔值

toBeNull 只匹配null

toBeTruthy 匹配任何为真的语句

toBeFalsy 匹配任何为假的语句

toBeDefined 与undefined相反

toBeUndefined 只匹配undefined

expect(xx).toBeUnDefined()

(4)、匹配对象

toMatchObject(object) 判断对象

toHaveProperty(keypath, value) 判断指定path下是否有这个属性以及值
javascript单元测试-为什么做、怎么做_第12张图片

(5)、其他一些断言使用

resolves 和 .rejects - 用来测试 promise
javascript单元测试-为什么做、怎么做_第13张图片
当然,你如果了解promise的用法,也可以使用其他办法来检查,比如:

检查红框中的返回值,返回的是一个promise对象
javascript单元测试-为什么做、怎么做_第14张图片
用例:
javascript单元测试-为什么做、怎么做_第15张图片
toHaveBeenCalled() - 用来判断一个函数是否被调用过,这个相当有用,在很多被测试的函数中,也许并没有返回值,但是会有一些回调函数作为参数传递给被测试函数,此时我们可以检查该回调函数是否被执行以及检查回调函数本身返回的数据;或者mock掉某个被调用函数之后,为了检查是否走入到该分支,也可以通过该mock函数是否被执行来检查测试。
javascript单元测试-为什么做、怎么做_第16张图片
toHaveBeenCalledTimes(number) - 判断函数被调用过几次
toContain() 判断实际值是否包含预期值
javascript单元测试-为什么做、怎么做_第17张图片
注:其他更多用法,可以参考官网~

4、覆盖率

Jest框架集成了覆盖率工具,执行的时候只需要加参数—coverage即可,如图:

​Jest –coverage ./ 会自动寻找当前目录下文件名带test的文件来执行
javascript单元测试-为什么做、怎么做_第18张图片
注意:这里展现的覆盖率,会把涉及到的文件都算进去,比如:被测试文件调用了abc三个文件,那abc这三个文件的覆盖率都会呈现出来,但其实我们关注的可能只是被测试文件这一个。
javascript单元测试-为什么做、怎么做_第19张图片

5、将结果写入指定文件

这个对于我们以后去做自定义结果展示的时候,会非常有用,可以把里面无用的信息剔除掉,只展示需要的信息即可;比如:你的被测试文件中调用了其他文件中的方法或者类,此时按照框架给的图标结果,他会将被测方法中调用的文件代码覆盖数据也会展示出来,但这个文件并不是我们想展示的。执行命令为:

jest --json --outputFile="./test.txt" 以json的格式输出并保存为test.txt文件中。


三、单元测试环境搭建

环境搭建比较简单,只需要简单的几步就可以搞定;

1、ide安装

这个根据直接喜好、习惯来安装就可以了,目前我这边使用的是webStorm, 还蛮好用。

2、node安装

本地执行js代码时,需要依赖node环境;下载完之后,直接安装即可。

安装好了之后,设置好环境变量,然后可以在命令窗输入node测试一下:
在这里插入图片描述
下载地址:https://nodejs.org/en/download/

3、npm安装

Npm是一个包管理工具,方便下载一些相关的依赖;

我们在安装node的时候,npm也被装上了:可以在命令窗直接输入npm -v测试一下:
javascript单元测试-为什么做、怎么做_第20张图片

4、jest 安装

可以使用npm来安装:

npm i jest -D 安装到本地

npm i jest -g 安装到全局

npm install --save-dev jest 开发依赖模式安装,需要到项目目录下之后,执行命令

如果是成功安装到本地,则在本地会有一个node_modules的文件夹:
javascript单元测试-为什么做、怎么做_第21张图片
本地安装的情况下, Jest命令也只能在当前目录中执行,在其他地方执行会报错。

安装好之后,可以直接执行jest命令, 如图:会执行URL目录下所有带test文件名中的用例,这里会自动采用正则去匹配。

5、jest配置

这里不配置也是可以直接跑起来的,如果需要一些定制的内容,则可以去配置文件中修改或者增加配置;可修改项目根目录下的package.json
javascript单元测试-为什么做、怎么做_第22张图片
可根据实际需要来配置:
详细可参考官网:https://jestjs.io/docs/zh-Hans/configuration


四、用例代码结构以及用例文件结构

1、用例代码结构

这里用例编写比较简单,站在不是编写被测试代码的一方,只要了解清楚需求实现,以及详细逻辑实现细节,读懂被测试代码,找出你要检查的点,基本上用例都很简单,前提当然是代码可测;目前我的写法是:一个方法对应一个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", ()=>{
		...
	});
});

2、用例文件结构

用例目录和被测试文件目录结构保持一致,且子目录名一样,用例文件中名字也基本一样,多了一个test,比如:被测试文件名为:index.js,用例名则为:index.test.js;当然这里你也可以使用其他规则去定义你的测试用例目录结构,只要方便查找查看就行。

3、用例开发节奏

实际项目中节奏:由于代码不多,基本上所有测试代码都由测试完成;

(1)底层实现,全部由测试来编写,和开发同步在分支上进行,进度会略慢,有问题的会反馈给开发,基本上手工测试验证完,用例可以开发并调试完成80%左右;

(2)代码上传周期:开发代码上传到分支,并非每天上传,这个需要跟开发沟通好,比如每天下班前提交一次代码。

(3)用例完成之后,以后每次开发再次改动代码必须执行完用例并且通过才能进行合入主干的操作。

可根据时间项目以及要求,动态调整节奏;


五、用例设计思路


六、遇到的问题以及解决办法

1、浏览器依赖

  1. 目前大部分的依赖,可以通过jsdom来解决;被测代码中,从浏览器中取值的情况下,基本上都可以解决,比如:url = window.location.href,可以在用例改变jsdom中的全局变量值,然后去重新设置window对象下的属性值;但是仍然有些问题,无法解决:
  2. 被测代码中取设置浏览器的某些属性,设置cookie,设置了之后,我们在用例中去调用时,发现取不到cookie,取出来仍然是jsdom的默认值;比如,被测代码中这样写:window.document.cookie = ’name=xx, age=yy‘;这个在浏览器里面执行,然后取出来,完全没有问题,但是在用例中没有什么好办法取到这个值;
  3. 如果在用例中设置了window.xx = yy;但是被测代码中将取值放在文件头,或者方法外,时间在取值的时候也是取不到,取到的仍然是jsdom中默认的值。比如如下图这种:UA取到的并不是我们设置后预期想要的。
    在这里插入图片描述

2、不太好检查的点以及不太适合的做单测的点

  1. 在浏览器中加载某个url请求
  2. 某些方法调用过于复杂,或者全是涉及到调用,没几行逻辑代码,这里感觉也可以不用做单测。
  3. 模块之间耦合太重
  4. 函数、方法实现过于长

3、mock 某个方法会提示方法为只读

  • 有个解决办法是:直接mock掉整个文件,然后再mock该方法的返回内容;

本文参考:
https://jestjs.io/docs

你可能感兴趣的:(单元测试,javascript,单元测试,js,java,编程语言)