关于Javascript单元测试,为什么要做以及怎么做(一)

发表于:2021-1-14 09:58

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:凯撒大帝jin    来源:CSDN

  前言
  为什么要做单测,怎么做
  做单测有没有必要?
  我想很多的研发同学都是予以肯定的答复,但是在实际的项目中去做单测的少之又少,而给出的不做单测的理由也是各式各样,且看起来都有自己的道理,比如:项目上线时间紧,人手少;成本太高,浪费时间;测试不是我来做,是测试同学来做的;后面的集成测试和系统测试会搞定所有bug的;那么实际上做单测不仅不会影响开发效率、上线时间,而且还是很有效的测试手段。
  那么做单测的理由,同样很充分:
  单元测试是最小颗粒度的测试,也是测试的第一个环节,也是最重要的一个环节;
  只有单元测试才可能让代码覆盖率达到100%;
  单元测试可以防止后期bug过多,而导致无法按时发布等失控情况的出现;
  80%的错误是在设计阶段引入,修正一个错误所需要的综合成本会随着研发周期逐步上升;
  单元测试可以发现集成测试和系统测试发现不了的问题。
  那么对于单测如何做?是不是所有的代码都要去做,对于这个问题,我们不能一刀切,视项目情况而定:
  对于新项目,在设计的时候就可以考虑尽可能的做单测;
  对于已经存在多年的存量代码,而且变动也不大,我认为不需要补单测了,理由:已经存在那么多年,经过了N多专业的测试以及外网用户的测试,该发现的问题,都已经发现,不能发现的问题,这样的场景几年都不遇到,也没必要花费代价去补一个可能发生的问题;
  对于迭代,新功能代码,可以尽量做单测(当然这里也要考虑实际情况,既然是迭代项目,如果老的代码结构设计不是太好,或者经过多人修改之后,代码存在严重耦合,而新的代码又无法避免的跟老的代码耦合在一起,这种情况,确实也不好做单测);
  对于前端,逻辑层、底层库等,这一类变动一般不会太大,尽量都做单元测试;
  对于前端,偏向UI的或者跟UI耦合重、变动频繁、兼容性多的,建议不做;
  目前在项目中,做单测起步是由测试来做,包括了:调研、框架选择、环境搭建、编写部分单测、效果展示、单测宣导(主要是给开发同学来),逐步推进;最终,我们是希望由开发来编写单测用例。
  一、框架选择
  为什么会选择jest框架?
  准备做之前,查了一下 js单元测试框架,发现框架很多:Mocha、Jasmine、Jest、Cucumber,相对比较流行的是Mocha和Jest,也了解了下各自的特点,发现Mocha上手起来会稍微复杂一点,Jest相对简单,但是Mocha的社区相对成熟,即能查的资料会相对多一些;
  Mocha对比Jest有哪些不便与方便呢?
  灵活:由于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方法一个返回值。
  这里是其中一种写法,只针对isIOS函数进行mock;
  场景2:其实也可以这样写,先mock Util,再给Util类下的方法返回值,这种是直接先将整个Util mock掉,这里跟模块的mock写法一样。
const mocktest = Jest.mock(‘Util’)
isIOS.mockReturnValueOnce(xx)
  景3:还有一种写法是直接在jest的对象中进行返回,如下图:
  检查回调函数是否被执行,或者需要创建一个函数来传递给被测试函数时,此时我们可以用到定义一个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
  反面例子,如图:
  (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(‘’)。
  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(`<!DOCTYPE html><p>Hello world</p>`);
console.log(dom.window.document.querySelector("p").textContent);
  (2)创建浏览器环境数据
  主要是设置像userAgent\href\location.localStorage等这些数据,因为这些数据会依赖当前脚本的执行环境,无法拿到我们期望设定的数据;直接在jsdom对象中设置属性值
  测试代码:
  (3)创建自定义数据
  自定义的数据,指的是业务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

  本文内容不用于商业目的,如涉及知识产权问题,请权利人联系51Testing小编(021-64471599-8017),我们将立即处理
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号