基础概念的介绍

单元测试(unit test):

单元测试是用来对一个模块、一个函数或者一个类来进行正确性检验的测试工作。比如写个加法函数add ( a,b ) {return a+b} ,我们可以编写出以下几个
测试用例如: .
输入1和1 ,期待返回结果是2
输入非数值类型,比如None.0]、 0} ,期待抛出异常。
把上面的测试用例放到一个测试模块里,就是一个完整的单元测试。
如果单元测试通过,说明我们测试的这个函数能够正常工作。如果单元测试不通过,要么函数有bug,要么测试条件输入不正确,总之,需要修复使单元测试能够通过。
那在Vue中的单元测试中主要使用两个工具分别是( Karma+Mocha )

Karma

Karma是一个基于Node.js的JavaScript测试执行过程管理工具( Test Runner)。该工具在Vue中的主要作用是将项目运行在各种主流Web浏览器进行测试。
换句话说,它是一个测试工具,能让你的代码在浏览器环境下测试。需要它的原因在于,你的代码可能是设计在浏览器端执行的,在node环境下测试可能有些bug暴露不出来;另外,浏览器有兼容问题,karma提供了手段让你的代码自动在多个浏览器( chrome , firefox , ie等)环境下运行。如果你的代码只会运行在node端,那么你不需要用karma。

Mocha

Mocha (发音摩卡)是一个测试框架,在vue- cli中配合Mocha本身不带断言库,所以必须先引入断言库, Chai断言库实现单元测试。
Mocha的常用命令和用法不算太多, 而Chai断言库可以看Chai.js断言库API中文文档,很简单,多查多用就能很快掌握。

断言库

所谓"断言” ,就是判断源码的实际执行结果与预期结果是否一致,如果不一致就抛出一个错误。下面这 句断言的意思是,调用add(1, 1) ,结果应该等于2。
var expect = require('chai).expect;expect(1 + 1)).to.be.equal(2);
Chai是一种断言库(http://chaijs.com/)
所有的测试用例( it块)都应该含有一句或多句的断言。它是编写测试用例的关键。断言功能由断言库来实现。

介绍完基本的概念之后正式开始实际的操作

一. 安装

(一) 使用脚手架初始化vue项目(使用webpack模板)
//命令行中输入
vue init webpack vueunittest

注意, 当询问到这一步Pick a test runner(Use arrow keys)时, 请选择使用Karma and Mocha
在这里插入图片描述

接下来的操作进入项目npm install安装相关依赖后(该步骤可能更会出现PhantomJS这个浏览器安装失败的报错, 不用理会, 因为 之后我们不使用这个浏览器), npm run build即可.

(二) 安装Karma-chrome-launch
npm install karma-chrome-launcher --save-dev

然后在项目中找到test/unit/karma.conf.js文件, 将PhantomJS浏览器修改为Chrome

//karma.conf.js

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

module.exports = function (config) {
  config.set({
    //browsers: ['PhantomJS'],
    browsers: ['Chrome'],    
    
    ...
  })
}
(三) 安装Vue-test-utils
npm install --save-dev vue-test-utils
(四) 执行npm run unit

当你完成以上两步的时候, 你就可以在命令行执行npm run unit尝鲜你的第一次单元测试了, Vue脚手架已经初始化了一个HelloWorld.spec.js的测试文件去测试HelloWrold.vue, 你可以在test/unit/specs/HelloWorld.spec.js下找到这个测试文件.(提示: 将来所有的测试文件, 都将放specs这个目录下, 并以测试脚本名.spec.js结尾命名!)

  • 在命令行输入npm run unit, 当你看到下图所示的一篇绿的时候, 说明你的单元测试通过了
    在这里插入图片描述

二. 测试工具的使用方法

下面是一个Counter.vue文件, 我将以该文件为基础讲解项目中测试工具的使用方法.

//Counter.vue

<template>
  <div>
    <h3>Counter.vue</h3>
    {{ count }}
    <button @click="increment">自增</button>
  </div>
</template>

<script>
  export default {
    data () {
      return {
        count: 0
      }
    },

    methods: {
      increment () {
        this.count++
      }
    }
  }
</script>
(一) Mocha框架
1. Mocha测试脚本的写法

Mocha的作用是运行测试脚本, 要对上面Counter.vue进行测试, 我们就要写测试脚本, 通常测试脚本应该与Vue组件名相同, 后缀为spec.js. 比如, Counter.vue组件的测试脚本名字就应该为Counter.spec.js

//Counter.spec.js

import Vue from 'vue'
import Counter from '@/components/Counter'

describe('Counter.vue', () => {

  it('点击按钮后, count的值应该为1', () => {
    //获取组件实例
    const Constructor = Vue.extend(Counter);
    //挂载组件
    const vm = new Constructor().$mount();
    //获取button
    const button = vm.$el.querySelector('button');
    //新建点击事件
    const clickEvent = new window.Event('click');
    //触发点击事件
    button.dispatchEvent(clickEvent);
    //监听点击事件
    vm._watcher.run();
    // 断言:count的值应该是数字1
    expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
  })

})

上面这段代码就是一个测试脚本.测试脚本应该包含一个或多个describe, 每个describe块应该包括一个或多个it块

describe块称为"测试套件"(test suite), 表示一组相关的测试. 它是一个函数, 第一个参数是测试套件的名称(通常写测试组件的名称, 这里即为Counter.js), 第二个参数是一个实际执行的函数.

it块称为"测试用例"(test case), 表示一个单独的测试, 是测试的最小单位. 它也是一个函数, 第一个参数是测试用例的名称(通常描述你的断言结果, 这里即为"点击按钮后, count的值应该为1"), 第二个参数是一个实际执行的函数.

2. Mocha进行异步测试

我们在Counter.vue组件中添加一个按钮, 并添加一个异步自增的方法为incrementByAsync, 该函数设置一个延时器, 1000ms后count自增1.

  <template>
    ...
    <button @click="increment">自增</button>
    <button @click="incrementByAsync">异步自增</button>
    ...
  <template>
  
  <script>
     ...
     methods: {
      ...
      incrementByAsync () {
        window.setTimeout(() => {
          this.count++;
        }, 1000) 
      }
    }
  </script>

给测试脚本中新增一个测试用例, 也就是it()
it(‘count异步更新, count的值应该为1’, (done) => {
///获取组件实例
const Constructor = Vue.extend(Counter);
//挂载组件
const vm = new Constructor(). m o u n t ( ) ; / / 获 取 b u t t o n c o n s t b u t t o n = v m . mount(); //获取button const button = vm. mount();//buttonconstbutton=vm.el.querySelectorAll(‘button’)[1];
//新建点击事件
const clickEvent = new window.Event(‘click’);

//触发点击事件
button.dispatchEvent(clickEvent);
//监听点击事件
vm._watcher.run();
//1s后进行断言
window.setTimeout(() => {
  // 断言:count的值应该是数字1
  expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);
  done();
}, 1000);

})

Mocha中的异步测试, 需要给it()内函数的参数中添加一个done, 并在异步执行完后必须调用done(), 如果不调用done(), 那么Mocha会在2000ms后报错且本次单元测试测试失败(mocha默认的异步测试超时上线为2000ms), 错误信息如下:
在这里插入图片描述

3. Mocha的测试钩子
describe('钩子说明', function() {

  before(function() {
    // 在本区块的所有测试用例之前执行
  });

  after(function() {
    // 在本区块的所有测试用例之后执行
  });

  beforeEach(function() {
    // 在本区块的每个测试用例之前执行
  });

  afterEach(function() {
    // 在本区块的每个测试用例之后执行
  });

});

Mocha官方文档 : https://mochajs.org/

(二) Chai断言库

上面的测试用例中, 以expect()方法开头的就是断言.

expect(Number(vm.$el.querySelector('.num').textContent)).to.equal(1);

所谓断言, 就是判断源码的实际执行结果与预期结果是否一致, 如果不一致, 就会抛出错误. 上面的断言的意思是指: 有.num这类名的节点的内容应该为数字1. 断言库库有很多种, Mocha并不限制你需要使用哪一种断言库, Vue的脚手架提供的断言库是sino-chai, 是一个基于Chai的断言库, 并且我们指定使用的是它的expect断言风格.
expect断言风格的优点很接近于自然语言, 下面是一些例子

  // 相等或不相等
  expect(1 + 1).to.be.equal(2);
  expect(1 + 1).to.be.not.equal(3);

  // 布尔值为true
  expect('hello').to.be.ok;
  expect(false).to.not.be.ok;

  // typeof
  expect('test').to.be.a('string');
  expect({ foo: 'bar' }).to.be.an('object');
  expect(foo).to.be.an.instanceof(Foo);

  // include
  expect([1,2,3]).to.include(2);
  expect('foobar').to.contain('foo');
  expect({ foo: 'bar', hello: 'universe' }).to.include.keys('foo');

  // empty
  expect([]).to.be.empty;
  expect('').to.be.empty;
  expect({}).to.be.empty;
  
  // match
  expect('foobar').to.match(/^foo/);

(三) Vue-test-utils测试库

1. 在测试脚本中引入vue-test-utils
//Counter.spec.js

import Vue from 'vue'
import Counter from '@/components/Counter'
//引入vue-test-utils
import {mount} from 'vue-test-utils'
2. 测试文本内容

下面我将在Counter.spec.js测试脚本中对Counter.vue中<h3>的文本内容进行测试, 大家可以直观的感受一下使用了Vue-test-utils后对.vue单文件组件的测试变得多么简单.

未使用vue-test-utils的测试用例:

  it('未使用Vue-test-utils: 正确渲染h3的文字为Counter.vue', () => {
    const Constructor = Vue.extend(Counter);
    const vm = new Constructor().$mount();
    const H3 = vm.$el.querySelector('h3').textContent;
    expect(H3).to.equal('Counter.vue');
  })

使用了vue-test-utils的测试用例:

  it('使用Vue-test-Utils: 正确渲染h3的文字为Counter.vue', () => {
    const wrapper = mount(Counter);
    expect(wrapper.find('h3').text()).to.equal('Counter.vue');
  })

从上面的代码可以看出, vue-test-utils工具将该测试用例的代码量减少了一半, 如果是更复杂的测试用例, 那么代码量的减少将更为突出. 它可以让我们更专注于去写文件的测试逻辑, 将获取组件实例和挂载的繁琐的操作交由vue-test-utils去完成.

3. vue-test-utils的常用API
  • find(): 返回匹配选择器的第一个DOM节点或Vue组件的wrapper, 可以使用任何有效的选择器
  • text(): 返回wrapper的文本内容
  • html(): 返回wrapper DOM的HTML字符串
  it('find()/text()/html()方法', () => {
    const wrapper = mount(Counter);
    const h3 = wrapper.find('h3');
    expect(h3.text()).to.equal('Counter.vue');
    expect(h3.html()).to.equal('<h3>Counter.vue</h3>');
  })
  • trigger(): 在该 wrapper DOM节点上触发一个事件。
it('trigger()方法', () => {
    const wrapper = mount(Counter);
    const buttonOfSync = wrapper.find('.sync-button');
    buttonOfSync.trigger('click');
    buttonOfSync.trigger('click');
    const count = Number(wrapper.find('.num').text());
    expect(count).to.equal(2);
  })
  • setData(): 设置data的属性并强制更新
  it('setData()方法',() => {
   const wrapper = mount(Counter);
   wrapper.setData({foo: 'bar'});
   expect(wrapper.vm.foo).to.equal('bar');
 })

以上就是我对单元测试的个人理解,欢迎批评指正

Logo

前往低代码交流专区

更多推荐