Karma+mocha+chai

三种工具简介

  1. Karma

    Karma官方网址
    Karma为前端自动化测试提供了跨浏览器测试的能力,可以自动在Chrome,Firefox,IE等主流浏览器依次跑完测试用例,同时也支持headless浏览器(入phantomJs)中运行测试用例。webpack+babel可以主动为想要适配的浏览器提供转码和垫片补丁引入能力,而Karma可以为最终的结果提供验证能力。Karma的配置方式可以阅读《webpack4.0各个击破(9)——Karma篇》进行了解。

  2. Mocha
    mocha是前端自动化测试框架,提供了不同风格断言库的兼容,测试用例分组,同步、异步测试架构,生命周期钩子等能力。

    基本用法

    describe('我现在要测某一个页面的几个功能',function(){
        describe('现在要测XX功能',function(){
            it('某个变量的值应该是数字',function(){
                //写断言
            })
        });
         describe('现在要测YY功能',function(){
            it('某个数组长度应该不小于10',function(){
                //写断言
            })
        });
    })
    

    异步测试

    describe('现在要测XX功能',function(){
        // done 传入done函数,在测试结束的时候调用,告知测试程序结束了,否则程序会等待超时
        it('异步请求应该返回一个对象', function(done){
    	  request
    	    .get('https://api.github.com')
    	    .end(function(err, res){
    	      expect(res).to.be.an('object');
    	      done();
    	    });
    	});
    	// promise的支持
    	it('异步请求应该返回一个对象', function() {
    	  return fetch('https://api.github.com')
    	    .then(function(res) {
    	      return res.json();
    	    }).then(function(json) {
    	      expect(json).to.be.an('object');
    	    });
    	});
    	// async await
        it('#async function', async () => {
    	    let r = await hello();
    	    assert.strictEqual(r, 15);
    	});
    });
    

    不同风格的断言库

    支持should.js,expect.js及node核心断言模块assert等。

    生命周期钩子

    describe('hooks', function() {
    
      before(function() {
        // 在本区块的所有测试用例之前执行
      });
    
      after(function() {
        // 在本区块的所有测试用例之后执行
      });
    
      beforeEach(function() {
        // 在本区块的每个测试用例之前执行
      });
    
      afterEach(function() {
        // 在本区块的每个测试用例之后执行
      });
    
      // test cases
    });
    

    生命周期钩子一般用来建立和清理环境或全局变量。

  3. chai
    Chai是一个断言库合集,支持expect,assert,should断言语法,非专业测试岗位其实没必要深究,了解使用方法就可以了。使用示例:

    expect(bar).to.not.exist;//断言变量bar不存在
    expect(data).to.have.ownProperty('length');//断言data有length属性
    expect(name).to.be.a('string');//断言name是一个字符串
    assert.equal(value1,value2);//断言value1和value2相等
    Tim.should.be.an.instanceof(Person);//断言Tim是Person类的实例
    

简单测试用例

// calculate.js
export default {
  add (a, b) {
    return a + b
  },
  minus (a, b) {
    return a - b - 1
  }
}


// calculate.spec.js
import calculate from '@/js/calculate'

describe('calculate test', () => {
  it('function add should return right sum', () => {
    expect(calculate.add(1, 2))
      .equal(3)
  })
  it('function minus should return right value', () => {
    expect(calculate.minus(2, 1)).equal(1)
  })
})

运行测试

// package.json
{
    "scripts": {
        "unit": "karma start karma.conf.js"
    }
}
===》 npm run unit

结果报告
Karma+mocha+chai
与vue结合的测试

import Vue from 'vue' // 导入Vue用于生成Vue实例
import Hello from '@/components/Hello' // 导入组件
// 测试脚本里面应该包括一个或多个describe块,称为测试套件(test suite)
describe('Hello.vue', () => {
  // 每个describe块应该包括一个或多个it块,称为测试用例(test case)
  it('should render correct contents', () => {
    const Constructor = Vue.extend(Hello) // 获得Hello组件实例
    const vm = new Constructor().$mount() // 将组件挂在到DOM上
    //断言:DOM中class为hello的元素中的h1元素的文本内容为Welcome to Your Vue.js App
    expect(vm.$el.querySelector('.hello h1').textContent)
      .to.equal('Welcome to Your Vue.js App')  
  })
})

karma.conf.js的配置

var webpackConfig = require('../../build/webpack.test.conf')

module.exports = function (config) {
  config.set({
  	// 插件
  	plugins: [
      'karma-chrome-launcher',
      'karma-webpack',
      'karma-mocha',
      'karma-chai',
      'karma-spec-reporter',
      'karma-coverage'
    ],
    // 浏览器
    browsers: ['PhantomJS'],
    // 测试框架
    frameworks: ['mocha', 'sinon-chai', 'phantomjs-shim'],
    // 测试报告
    reporters: ['spec', 'coverage'],
    // 测试入口文件
    files: ['./index.js'],
    // 预处理器 karma-webpack
    preprocessors: {
      './index.js': ['webpack', 'sourcemap']
    },
    // Webpack配置
    webpack: webpackConfig,
    // Webpack中间件
    webpackMiddleware: {
      noInfo: true
    },
    // 测试覆盖率报告
    // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md
    coverageReporter: {
      dir: './coverage',
      reporters: [
        { type: 'lcov', subdir: '.' },
        { type: 'text-summary' }
      ]
    }
  })
}

util.js
从Vue官方的demo可以看出,对于Vue的单元测试我们需要将组件实例化为一个Vue实例,有时还需要挂载到DOM上,推荐Element的单元测试工具脚本Util.js,它封装了Vue单元测试中常用的方法

import Vue from 'vue'
import Element from 'element-ui'
Vue.use(Element)

let id = 0

const createElm = function () {
  const elm = document.createElement('div')

  elm.id = 'app' + ++id
  document.body.appendChild(elm)

  return elm
}

/**
 * 回收 vm
 * @param  {Object} vm
 */
exports.destroyVM = function (vm) {
  vm.$destroy && vm.$destroy()
  vm.$el &&
  vm.$el.parentNode &&
  vm.$el.parentNode.removeChild(vm.$el)
}

/**
 * 创建一个 Vue 的实例对象
 * @param  {Object|String}  Compo   组件配置,可直接传 template
 * @param  {Boolean=false} mounted 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createVue = function (Compo, mounted = false) {
  if (Object.prototype.toString.call(Compo) === '[object String]') {
    Compo = { template: Compo }
  }
  return new Vue(Compo).$mount(mounted === false ? null : createElm())
}

/**
 * 创建一个测试组件实例
 * @link http://vuejs.org/guide/unit-testing.html#Writing-Testable-Components
 * @param  {Object}  Compo          - 组件对象
 * @param  {Object}  propsData      - props 数据
 * @param  {Boolean=false} mounted  - 是否添加到 DOM 上
 * @return {Object} vm
 */
exports.createTest = function (Compo, propsData = {}, mounted = false) {
  if (propsData === true || propsData === false) {
    mounted = propsData
    propsData = {}
  }
  const elm = createElm()
  const Ctor = Vue.extend(Compo)
  return new Ctor({ propsData }).$mount(mounted === false ? null : elm)
}

/**
 * 触发一个事件
 * mouseenter, mouseleave, mouseover, keyup, change, click 等
 * @param  {Element} elm
 * @param  {String} name
 * @param  {*} opts
 */
exports.triggerEvent = function (elm, name, ...opts) {
  let eventName

  if (/^mouse|click/.test(name)) {
    eventName = 'MouseEvents'
  } else if (/^key/.test(name)) {
    eventName = 'KeyboardEvent'
  } else {
    eventName = 'HTMLEvents'
  }
  const evt = document.createEvent(eventName)

  evt.initEvent(name, ...opts)
  elm.dispatchEvent
    ? elm.dispatchEvent(evt)
    : elm.fireEvent('on' + name, evt)

  return elm
}

/**
 * 触发 “mouseup” 和 “mousedown” 事件
 * @param {Element} elm
 * @param {*} opts
 */
exports.triggerClick = function (elm, ...opts) {
  exports.triggerEvent(elm, 'mousedown', ...opts)
  exports.triggerEvent(elm, 'mouseup', ...opts)

  return elm
}

/**
 * 触发 keydown 事件
 * @param {Element} elm
 * @param {keyCode} int
 */
exports.triggerKeyDown = function (el, keyCode) {
  const evt = document.createEvent('Events')
  evt.initEvent('keydown', true, true)
  evt.keyCode = keyCode
  el.dispatchEvent(evt)
}

使用的例子

// Hello.vue
<template>
  <div class="hello">
    <h1 class="hello-title">{{ msg }}</h1>
    <h2 class="hello-content">{{ content }}</h2>
  </div>
</template>

<script>
export default {
  name: 'hello',
  props: {
    content: String
  },
  data () {
    return {
      msg: 'Welcome!'
    }
  }
}
</script>
// Hello.spec.js
import { destroyVM, createTest } from '../util'
import Hello from '@/components/Hello'

describe('Hello.vue', () => {
  let vm

  afterEach(() => {
    destroyVM(vm)
  })

  it('测试获取元素内容', () => {
    vm = createTest(Hello, { content: 'Hello World' }, true)
    expect(vm.$el.querySelector('.hello h1').textContent).to.equal('Welcome!')
    expect(vm.$el.querySelector('.hello h2').textContent).to.have.be.equal('Hello World')
  })

  it('测试获取Vue对象中数据', () => {
    vm = createTest(Hello, { content: 'Hello World' }, true)
    expect(vm.msg).to.equal('Welcome!')
    // Chai的语言链是无意义的,可以随便写。如下:
    expect(vm.content).which.have.to.be.that.equal('Hello World') 
  })

  it('测试获取DOM中是否存在某个class', () => {
    vm = createTest(Hello, { content: 'Hello World' }, true)
    expect(vm.$el.classList.contains('hello')).to.be.true
    const title = vm.$el.querySelector('.hello h1')
    expect(title.classList.contains('hello-title')).to.be.true
    const content = vm.$el.querySelector('.hello-content')
    expect(content.classList.contains('hello-content')).to.be.true
  })
})

测试结果:

Hello.vue
  √ 测试获取元素内容
  √ 测试获取Vue对象中数据
  √ 测试获取DOM中是否存在某个class

测试注意:
1、 测试脚本都要放在 test/unit/specs/ 目录下
2、 脚本命名方式为 [组件名].spec.js
3、所谓断言,就是对组件做一些操作,并预言产生的结果。如果测试结果与断言相同则测试通过
4、单元测试默认测试 src 目录下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改
5、Chai断言库中,to be been is that which and has have with at of same 这些语言链是没有意义的,只是便于理解而已

参考博客:
https://blog.51cto.com/13869008/2175983
https://www.jianshu.com/p/34afa645487b
https://blog.csdn.net/qq_34179086/article/details/89496825

上一篇:Omi原理-环境搭建


下一篇:Angular目录结构