单元测试整理

栏目: 编程工具 · 发布时间: 6年前

内容简介:学习单元测试的时候接触了很多概念karma、mocha、Jesmine、chai、expect、assert、should、sinon等,容易混乱,在此做个梳理。入门文章参考:Mocha是一个常用的JS测试框架,可以在浏览器和Nodejs环境使用。Mocha不带断言需要和断言库结合使用。项目中使用的也是Mocha+chai+sinon的结合。例如给ndfront组件写的单元测试,详情查看github仓库:

学习单元测试的时候接触了很多概念karma、mocha、Jesmine、chai、expect、assert、should、sinon等,容易混乱,在此做个梳理。

1. 测试框架 Mocha、Jesmine

1.1 Mocha

入门文章参考: www.ruanyifeng.com/blog/2015/1…

Mocha是一个常用的JS测试框架,可以在浏览器和Nodejs环境使用。Mocha不带断言需要和断言库结合使用。项目中使用的也是Mocha+chai+sinon的结合。例如给ndfront组件写的单元测试,详情查看github仓库: github.com/baihexx/ndf…

特点:灵活,可扩展性好,可配合不同的断言库使用,但是自身集成度不高

1.2 Jesmine

Jesmine也是常用的测试框架,项目中没有用这个。

特点:内置断言库,集成度高,方便支持异步测试,但是灵活性差,断言风格单一

2. 断言库

2.1 assert

assert模块是Node的内置模块,用于断言。

官方API

常用API

eg:

var assert = requier('assert')
describe('desc1', function() {
    it('desc2', function() {
        assert(a === 1, '预期a的值是1')
    })
})
复制代码

2.2 should.js

should.js是个第三方断言库,常和Mocha联合使用。

  • 使用方法1:

    requier('should'): 扩展Object.prototype,增加should属性,所有Object可以直接获取should使用,eg:

var should = require('should');
(5).should.be.exactly(5).and.be.a.Number();

var a = null
a.should.not.be.ok() // 报错
复制代码
  • 使用方法2:

    若是undefined或者null,并没有继承Object的原型链,没有should属性可用,可采用如下方法:

var should = require('should/as-function');
var a = null
should(a).not.be.ok() // pass
should(10).be.exactly(5).and.be.a.Number();
复制代码

2.3 Chai

官网API: 安装方法等查看官网

Chai是个断言库,常和Mocha结合使用。他有多种断言风格(assertion style):assert, expect, should

  • assert断言风格:和nodejs的assert模块类似(多了写语法糖),是一种非链式语言风格 eg:
var assert = requier('chai').assert
assert.notEqual(3, 4, 'these numbers are not equal')
复制代码
  • expect断言风格:expect和should都是BDD风格,是一种链式语言风格, 连接词有 to,be,been,is等自然语言, eg:
var expect = requier('Chai').expect
expect([1, 2, 3]).to.be.an('array').that.includes(2)
复制代码
  • should断言风格:should()扩展了Object.prototype,增加了should属性,使用方法如下。eg:
var should = require('chai').should() //actually call the function
var foo = 'bar'
foo.should.be.a('string')
foo.should.equal('bar')
foo.should.have.lengthOf(3)
复制代码

(注意should对IE兼容性不好)

3. 测试运行工具:karma

Github 仓库

定义:A simple tool that allows you to execute JavaScript code in multiple real browsers. The main purpose of Karma is to make your test-driven development easy, fast, and fun.

Karma不是测试框架,也不是断言库,他会开启一个HTTP服务,将测试文件生成一个Html文件,在浏览器内运行、调试。Karma不指定测试框架,通过插件和Mocha、Jesmine、QUnit都可以结合使用。

配置项较多,根据官网说明配置,并不难。

测试覆盖率 :根据提示安装、配置即可生成覆盖率报告

4. 测试辅助工具:Sinon

为什么需要Sinon?在做单元测试的时候,我们会发现我们要测试的方法会引用很多外部依赖的对象,比如:(发送邮件,网络通讯,记录Log, 文件系统之类的),而我们没法控制这些外部依赖的对象。例如:前端项目通常是用Ajax去服务端请求数据,得到数据之后做进一步的处理。但是做单元测试的时候通常不真的去服务端请求数据,不仅麻烦,可能服务端接口还没做好,这种不确定的依赖使得测试变得复杂。所以我们需要模拟这个请求数据的过程,Sinon用来解决这个问题。

Sinon的工作本质是“测试替身”,测试替身用来替换测试中的部分代码,使得测试复杂代码变得简单。

Sinon提供了三个功能:示例讲解看入门文章,不再赘述

  • spy(间谍):提供函数调用的信息,但不会改变函数的行为
  • stub:与spies类似,但是会完全替换目标函数。这使得一个被stubbed的函数可以做任何你想要的 —— 例如抛出一个异常,返回某个特定值等等。
  • mock:通过组合spies和stubs,使替换一个完整对象更容易

eg:admin/misc/user.js: user.getUser() (看不懂的随便看看,这是实际的项目代码单元测试)

user.getUser函数用来获取用户信息,用户输入工号后向服务端请求数据,我们的测试用例重点在前端代码,不应依赖服务端才可测试,so 应该模拟ajax请求。

(1)nd-spa中ajax.js请求代码如下:实际的请求函数为:request.get, so应该mock request的get函数

单元测试整理
单元测试整理

(2)测试用例代码如下:

单元测试整理
  • sinon.stub(obj, functionname, mockFun)
  • ajax.js中的请求代码调用的set(),send(),end()函数实际是mock中定义的函数,并没有做实际的后端请求,end的callback直接返回数据
  • 注意L11:设置函数最大时长,写成function(done)形式才可用this.timeout,否则es6的箭头函数中this是window

5. 多步骤测试用例(实际项目记录,可不看,估计看不明白)

用户在前端页面中通常是通过点击鼠标期待某种效果,这个过程通常不做单元测试,因为复杂度较高,且页面变动较快,性价比很低。但是项目中某些公共的业务组件,需求变动小,步骤相对简单,但使用又非常多,例如social管理后台中的搜索用户admin/misc/user.js:user.autoComplete(),完整的过程是:用户输入用户信息,然后选择搜索到的匹配用户,再点击搜索到的用户,该输入框的值变为选择的用户。这个过程如何编写单元测试。

这里涉及到多步骤的模拟。

(1)util.js 封装多步执行函数

// utils.js
export function triggerHTMLEvents (target, event, process) {
  const e = document.createEvent('HTMLEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

export function triggerMouseEvents (target, event, process) {
  const e = document.createEvent('MouseEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

export function triggerUIEvents (target, event, process) {
  const e = document.createEvent('UIEvents')
  e.initEvent(event, true, true)
  if (process) process(e)
  target.dispatchEvent(e)
  return e
}

// timeout: 执行间隔可设置
function createStep ({ step, timeout }) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      try {
        resolve(step())
      } catch (err) {
        reject(err)
      }
    }, timeout || 0)
  })
}
// 多步骤执行函数, 执行步骤封装在arr数组中
export function runSteps (arr) {
  if (arr.length === 0) {
    return
  }
  let firstStep = createStep(arr[0])
  const others = arr.splice(1)
  others.forEach(item => {
    firstStep = firstStep.then(() => {
      return createStep(item)
    })
  })
}
复制代码

(2)使用

单元测试整理

L116:给input设置值

L117:触发input的change事件,执行nd-autocomplete/src/input.js中change事件,如何调用的看autocomplete组件 L122:点击搜索列表的item,将值设置到input,然后验证input的值

过程中遇到一个额外的问题,在此记录下,备忘

(1)user中getUsers函数用到ucOrgId,查看代码(var ucOrgId = auth.getAuth('uc_org_id') 且仅仅在登录代码中有auth.setAuth())可知该组织id信息必须有,但是测试页面中没有登录,获取不到该信息,so,造登录数据,并设置到auth中。

(2)造登录数据并设置到auth中时遇到一个问题:若在user.spec.js中 引入auth,then auth.setAuth(...), 结果不对

原因:user.js中在开头就执行了获取ucOrgId的函数,我们在测试代码中先引入auth和user,这时ucOrgId已经获取了,且是空值,即使假造登录数据的函数写在import user之前也是没用,因为es6有提升的功能,总是先执行import,导致函数执行在后面,解决方法是:将设置登录数据的函数写在单独的文件中,使用import的方法,且在user之前inport,就会达到先执行设置登录数据的效果。(具体看代码更清晰)

(import的提升再复习下)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Pro HTML5 and CSS3 Design Patterns

Pro HTML5 and CSS3 Design Patterns

Michael Bowers / Apress / 2011-11-15 / GBP 35.50

Pro HTML5 and CSS3 Design Patterns is a reference book and a cookbook on how to style web pages using CSS3 and HTML5. It contains 350 ready--to--use patterns (CSS3 and HTML5 code snippets) that you ca......一起来看看 《Pro HTML5 and CSS3 Design Patterns》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HSV CMYK 转换工具
HSV CMYK 转换工具

HSV CMYK互换工具