当前位置: 代码迷 >> 综合 >> Karma+mocha+chai
  详细解决方案

Karma+mocha+chai

热度:15   发布时间:2024-01-27 06:26:33.0

三种工具简介

  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 awaitit('#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

结果报告
在这里插入图片描述
与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 Appexpect(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-webpackpreprocessors: {'./index.js': ['webpack', 'sourcemap']},// Webpack配置webpack: webpackConfig,// Webpack中间件webpackMiddleware: {noInfo: true},// 测试覆盖率报告// https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.mdcoverageReporter: {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 = 0const createElm = function () {const elm = document.createElement('div')elm.id = 'app' + ++iddocument.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 = propsDatapropsData = {}}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 eventNameif (/^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 = keyCodeel.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 vmafterEach(() => {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.trueconst title = vm.$el.querySelector('.hello h1')expect(title.classList.contains('hello-title')).to.be.trueconst 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