??百度網(wǎng)盤(pán)??

提取碼:u6C4 

在使用vue-cli創(chuàng)建項(xiàng)目的時(shí)候,會(huì)提示要不要安裝單元測(cè)試和e2e測(cè)試。既然官方推薦我們使用這兩個(gè)測(cè)試框架,那么我們就動(dòng)手去學(xué)習(xí)實(shí)踐一下他們吧。

簡(jiǎn)介


Karma


Karma是一個(gè)基于Node.js的JavaScript測(cè)試執(zhí)行過(guò)程管理工具(Test Runner)。該工具在Vue中的主要作用是將項(xiàng)目運(yùn)行在各種主流Web瀏覽器進(jìn)行測(cè)試。

換句話說(shuō),它是一個(gè)測(cè)試工具,能讓你的代碼在瀏覽器環(huán)境下測(cè)試。需要它的原因在于,你的代碼可能是設(shè)計(jì)在瀏覽器端執(zhí)行的,在node環(huán)境下測(cè)試可能有些bug暴露不出來(lái);另外,瀏覽器有兼容問(wèn)題,karma提供了手段讓你的代碼自動(dòng)在多個(gè)瀏覽器(chrome,firefox,ie等)環(huán)境下運(yùn)行。如果你的代碼只會(huì)運(yùn)行在node端,那么你不需要用karma。


Mocha


Mocha是一個(gè)測(cè)試框架,在vue-cli中配合chai斷言庫(kù)實(shí)現(xiàn)單元測(cè)試。

Mocha的常用命令和用法不算太多,看阮一峰老師的測(cè)試框架 Mocha 實(shí)例教程就可以大致了解了。

而Chai斷言庫(kù)可以看Chai.js斷言庫(kù)API中文文檔,很簡(jiǎn)單,多查多用就能很快掌握。


我對(duì)測(cè)試框架的理解


npm run unit 執(zhí)行過(guò)程


執(zhí)行 npm run unit 命令

開(kāi)啟Karma運(yùn)行環(huán)境

使用Mocha去逐個(gè)測(cè)試用Chai斷言寫(xiě)的測(cè)試用例

在終端顯示測(cè)試結(jié)果

如果測(cè)試成功,karma-coverage 會(huì)在 ./test/unit/coverage 文件夾中生成測(cè)試覆蓋率結(jié)果的網(wǎng)頁(yè)。

Karma


對(duì)于Karma,我只是了解了一下它的配置選項(xiàng)。

下面是Vue的karma配置,簡(jiǎn)單注釋了下:


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


module.exports = function (config) {

 config.set({

   // 瀏覽器

   browsers: [PhantomJS],

   // 測(cè)試框架

   frameworks: [mocha, sinon-chai, phantomjs-shim],

   // 測(cè)試報(bào)告

   reporters: [spec, coverage],

   // 測(cè)試入口文件

   files: [./index.js],

   // 預(yù)處理器 karma-webpack

   preprocessors: {

     ./index.js: [webpack, sourcemap]

   },

   // Webpack配置

   webpack: webpackConfig,

   // Webpack中間件

   webpackMiddleware: {

     noInfo: true

   },

   // 測(cè)試覆蓋率報(bào)告

   // https://github.com/karma-runner/karma-coverage/blob/master/docs/configuration.md

   coverageReporter: {

     dir: ./coverage,

     reporters: [

       { type: lcov, subdir: . },

       { type: text-summary }

     ]

   }

 })

}


Mocha和chai


我們看下官方的例子(都用注釋來(lái)解釋代碼意思了):


import Vue from vue // 導(dǎo)入Vue用于生成Vue實(shí)例

import Hello from @/components/Hello // 導(dǎo)入組件

// 測(cè)試腳本里面應(yīng)該包括一個(gè)或多個(gè)describe塊,稱(chēng)為測(cè)試套件(test suite)

describe(Hello.vue, () => {

 // 每個(gè)describe塊應(yīng)該包括一個(gè)或多個(gè)it塊,稱(chēng)為測(cè)試用例(test case)

 it(should render correct contents, () => {

   const Constructor = Vue.extend(Hello) // 獲得Hello組件實(shí)例

   const vm = new Constructor().$mount() // 將組件掛在到DOM上

   //斷言:DOM中class為hello的元素中的h1元素的文本內(nèi)容為Welcome to Your Vue.js App

   expect(vm.$el.querySelector(.hello h1).textContent)

     .to.equal(Welcome to Your Vue.js App)  

 })

})


需要知道的知識(shí)點(diǎn):


測(cè)試腳本都要放在 test/unit/specs/ 目錄下。

腳本命名方式為  [組件名].spec.js。

所謂斷言,就是對(duì)組件做一些操作,并預(yù)言產(chǎn)生的結(jié)果。如果測(cè)試結(jié)果與斷言相同則測(cè)試通過(guò)。

單元測(cè)試默認(rèn)測(cè)試 src 目錄下除了 main.js 之外的所有文件,可在 test/unit/index.js 文件中修改。

Chai斷言庫(kù)中,to be been is that which and has have with at of same 這些語(yǔ)言鏈?zhǔn)菦](méi)有意義的,只是便于理解而已。

測(cè)試腳本由多個(gè)  descibe 組成,每個(gè) describe 由多個(gè) it 組成。

了解異步測(cè)試

it(異步請(qǐng)求應(yīng)該返回一個(gè)對(duì)象, done => {

   request

   .get(https://api.github.com)

   .end(function(err, res){

     expect(res).to.be.an(object);

     done();

   });

});

了解一下 describe 的鉤子(生命周期)

describe(hooks, function() {


 before(function() {

   // 在本區(qū)塊的所有測(cè)試用例之前執(zhí)行

 });


 after(function() {

   // 在本區(qū)塊的所有測(cè)試用例之后執(zhí)行

 });


 beforeEach(function() {

   // 在本區(qū)塊的每個(gè)測(cè)試用例之前執(zhí)行

 });


 afterEach(function() {

   // 在本區(qū)塊的每個(gè)測(cè)試用例之后執(zhí)行

 });


 // test cases

});

實(shí)踐


上面簡(jiǎn)單介紹了單元測(cè)試的用法,下面來(lái)動(dòng)手在Vue中進(jìn)行單元測(cè)試!


util.js


從Vue官方的demo可以看出,對(duì)于Vue的單元測(cè)試我們需要將組件實(shí)例化為一個(gè)Vue實(shí)例,有時(shí)還需要掛載到DOM上。


const Constructor = Vue.extend(Hello) // 獲得Hello組件實(shí)例

const vm = new Constructor().$mount() // 將組件掛載到DOM上

以上寫(xiě)法只是簡(jiǎn)單的獲取組件,有時(shí)候我們需要傳遞props屬性、自定義方法等,還有可能我們需要用到第三方UI框架。所以以上寫(xiě)法非常麻煩。

這里推薦Element的單元測(cè)試工具腳本Util.js,它封裝了Vue單元測(cè)試中常用的方法。下面demo也是根據(jù)該 Util.js來(lái)寫(xiě)的。

這里簡(jiǎn)單注釋了下各方法的用途。


/**

* 回收 vm,一般在每個(gè)測(cè)試腳本測(cè)試完成后執(zhí)行回收vm。

* @param  {Object} vm

*/

exports.destroyVM = function (vm) {}


/**

* 創(chuàng)建一個(gè) Vue 的實(shí)例對(duì)象

* @param  {Object|String}  Compo     - 組件配置,可直接傳 template

* @param  {Boolean=false}  mounted   - 是否添加到 DOM 上

* @return {Object} vm

*/

exports.createVue = function (Compo, mounted = false) {}


/**

* 創(chuàng)建一個(gè)測(cè)試組件實(shí)例

* @param  {Object}  Compo          - 組件對(duì)象

* @param  {Object}  propsData      - props 數(shù)據(jù)

* @param  {Boolean=false} mounted  - 是否添加到 DOM 上

* @return {Object} vm

*/

exports.createTest = function (Compo, propsData = {}, mounted = false) {}


/**

* 觸發(fā)一個(gè)事件

* 注: 一般在觸發(fā)事件后使用 vm.$nextTick 方法確定事件觸發(fā)完成。

* mouseenter, mouseleave, mouseover, keyup, change, click 等

* @param  {Element} elm      - 元素

* @param  {String} name      - 事件名稱(chēng)

* @param  {*} opts           - 配置項(xiàng)

*/

exports.triggerEvent = function (elm, name, ...opts) {}


/**

* 觸發(fā) “mouseup” 和 “mousedown” 事件,既觸發(fā)點(diǎn)擊事件。

* @param {Element} elm     - 元素

* @param {*} opts          - 配置選項(xiàng)

*/

exports.triggerClick = function (elm, ...opts) {}

示例一


示例一中我們測(cè)試了 Hello 組件的各種元素的數(shù)據(jù),學(xué)習(xí)  util.js 的 destroyVM 和 createTest 方法的用法以及如何獲取目標(biāo)元素進(jìn)行測(cè)試。獲取DOM元素的方式可查看DOM 對(duì)象教程。

Hello.vue



Hello.spec.js


import { destroyVM, createTest } from ../util

import Hello from @/components/Hello


describe(Hello.vue, () => {

 let vm


 afterEach(() => {

   destroyVM(vm)

 })


 it(測(cè)試獲取元素內(nèi)容, () => {

   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(測(cè)試獲取Vue對(duì)象中數(shù)據(jù), () => {

   vm = createTest(Hello, { content: Hello World }, true)

   expect(vm.msg).to.equal(Welcome!)

   // Chai的語(yǔ)言鏈?zhǔn)菬o(wú)意義的,可以隨便寫(xiě)。如下:

   expect(vm.content).which.have.to.be.that.equal(Hello World)

 })


 it(測(cè)試獲取DOM中是否存在某個(gè)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

 })

})

輸出結(jié)果


Hello.vue

 √ 測(cè)試獲取元素內(nèi)容

 √ 測(cè)試獲取Vue對(duì)象中數(shù)據(jù)

 √ 測(cè)試獲取DOM中是否存在某個(gè)class

示例二


示例二中我們使用 createTest 創(chuàng)建測(cè)試組件測(cè)試點(diǎn)擊事件,用 createVue 創(chuàng)建Vue示例對(duì)象測(cè)試組件 Click 的使用。這里主要可以看下到 createVue 方法的使用。

Click.vue




Click.spec.js


import { destroyVM, createTest, createVue } from ../util

import Click from @/components/Click


describe(click.vue, () => {

 let vm


 afterEach(() => {

   destroyVM(vm)

 })


 it(測(cè)試按鈕點(diǎn)擊事件, () => {

   vm = createTest(Click, {

     AddNum: 10,

     InitNum: 11

   }, true)

   let buttonElm = vm.$el.querySelector(button)

   buttonElm.click()

   buttonElm.click()

   buttonElm.click()

   // setTimeout 的原因

   // 在數(shù)據(jù)改變之后,界面的變化會(huì)有一定延時(shí)。不用timeout有時(shí)候會(huì)發(fā)現(xiàn)界面沒(méi)有變化

   setTimeout(done => {

     expect(vm.ResultNum).to.equal(41)

     expect(vm.$el.querySelector(.init-num).textContent).to.equal(初始值為11)

     expect(vm.$el.querySelector(.click-num).textContent).to.equal(點(diǎn)擊了3次)

     expect(vm.$el.querySelector(.result-num).textContent).to.equal(最終結(jié)果為41)

     done()

   }, 100)

 })


 it(測(cè)試創(chuàng)建Vue對(duì)象, () => {

   let result

   vm = createVue({

     template: `

       

     `,

     props: {

       AddNum: 10,

       InitNum: 11

     },

     methods: {

       handleClick (obj) {

         result = obj

       }

     },

     components: {

       Click

     }

   }, true)

   vm.$el.click()

   vm.$nextTick(done => {

     expect(result).to.be.exist

     expect(result.ClickNum).to.equal(1)

     expect(result.ResultNum).to.be.equal(21)

     done()

   })

})

輸出結(jié)果


click.vue

 √ 測(cè)試按鈕點(diǎn)擊事件

 √ 測(cè)試創(chuàng)建Vue對(duì)象

其他


所有示例代碼都放Github倉(cāng)庫(kù)中便于查看。如果想查看更多好的測(cè)試用例,建議配合 Util.js 看一下 Element 的單元測(cè)試腳本的寫(xiě)法,里面有很多測(cè)試腳本可以供我們學(xué)習(xí)。作為被廣大Vue用戶(hù)使用的UI組件庫(kù),測(cè)試腳本肯定也寫(xiě)很很不錯(cuò)的~甚至可以將這些腳本照抄一遍,相信這會(huì)對(duì)學(xué)習(xí)Vue組件的單元測(cè)試有很大幫助。


下面是本人看Element單元測(cè)試的筆記,供參考。


Util.js 方法包含了大多數(shù)Vue組件化的測(cè)試需求。

vm.$el vm.$nextTick 和 vm.$ref 都是在測(cè)試過(guò)程中比較常用的一些Vue語(yǔ)法糖。

需要注意: vm.$nextTick 方法是異步的,所以需要在里面使用done方法。

異步斷言,方法參數(shù)需要是 _ 或者 done

大多數(shù)時(shí)候查詢(xún)?cè)赝ㄟ^(guò) querySelector 方法查詢(xún)class獲得

vm.$el.querySelector(.el-breadcrumb).innerText

大多數(shù)情況下查詢(xún)是否存在某個(gè)Class通過(guò) classList.contains 方法獲得,查找的結(jié)果為 true 或 false

vm.$el .classList.contains(el-button--primary)

異步測(cè)試必須以 done() 方法結(jié)尾。setTimeout 和 vm.$nextTick 是常用的異步測(cè)試。

實(shí)現(xiàn)按鈕點(diǎn)擊:通過(guò)獲取按鈕元素 btn,執(zhí)行 btn.click() 方法實(shí)現(xiàn)。

由于 Vue 進(jìn)行 異步更新DOM 的情況,一些依賴(lài)DOM更新結(jié)果的斷言必須在 Vue.nextTick 回調(diào)中進(jìn)行。