單元測試
在建立項目的時候就将“單元測試”這個選項選上,CLI腳手架會自動幫助生成單元測試檔案,依賴庫都會幫助安裝好。
a)jest和mocha。js是由facebook開發的,不需要自己去寫js DOM以及斷言,已經内置了斷言庫。
b)@vue/test-utils
c) sinon:建立測試的替身
先自己想想測試該怎麼做。先将這個測試單元取個名字,說明是要測試哪個單元的程式。然後分析對這個程式要進行哪些角度的測試。如果是一個框,則可以從視圖和計算結果兩個方面來測試是否正确,視圖可以測試與快照是否相同,不同的話就不通過。計算結果的話就要先寫出輸入内容和輸出結果,如果網頁上的輸出結果跟測試單元裡面的輸出結果是一樣的就是對的。
以下這個Vue執行個體,功能有:每點一次按鈕數字就要加1,同時還要将count目前的值(this.count)發射到“change”這個頻道中,由它的父元件通過偵聽來接收。
Counter.vue

單元測試檔案不是vue執行個體,是js檔案
import { mount } from "@vue/test-utils"; //mount我的了解是錨定,就是複制(鏡像)需要測試的Vue執行個體,類似DPI将流量鏡像一遍進行檢查。
import Counter from "@/components/Counter.vue"; //将單元測試的對象import到單元測試的vue中,以便後續錨定。要測的就是Counter.vue
import sinon from "sinon"; //一個輔助測試的插件
describe("Counter.vue", () => { //給本單元測試取個名字
const change = sinon.spy(); //這個sinon.spy()函數就是安插在wrapper(Counter的複制品)中的一個間諜函數,負責攔截執行個體發出去的資訊。是以如果sinon.spy()函數擷取了值,而且這個值==this.count的話,就說明Count這個執行個體的emit功能沒有問題。
const wrapper = mount(Counter, { //錨定Counter這個vue執行個體
listeners: {
change //wrapper要監聽間諜change的動态。
}
});
it("renders counter html", () => { //it表示一個單元測試,它的名字叫做"renders counter html",wrapper就是Counter的複制品,這個複制品的html展現形式将與Counter本身的快照進行對比,如果一樣就表示測試通過。對比快照用指令toMatchSnapshot()。
expect(wrapper.html()).toMatchSnapshot();
});
it("count++", () => { //it表示另外一個針對Counter的單元測試,名字叫做“count++”。
const button = wrapper.find("button"); //在wrapper(即Counter的複制品)中找到button這個按鈕
button.trigger("click"); //然後通過click動作觸發button這個按鈕
expect(wrapper.vm.count).toBe(1); //點選按鈕後希望數值為1
expect(change.called).toBe(true); //點選按鈕後希望間諜被觸發(change.called=true)
button.trigger("click"); //再一次點選按鈕
expect(change.callCount).toBe(2); //希望間諜被觸發的次數為2
});
});
以上是從唐金州的視訊中的例子。
-----------------------------------------------------------------------------
以下是從“2小時學會vue單元測試”中學習到的例子。安裝的不是jest,是mocha,斷言庫用的是chai
https://www.bilibili.com/video/BV18W411j725?p=5&spm_id_from=pageDriver
TestDemo.vue 這是從vue的官網的vue單元測試的例子
<template>
<div>
<input v-model="username">
<div
v-if="error"
class="error"
>
{{ error }}
</div>
</div>
</template>
<script>
export default {
name: 'Hello',
data () {
return {
username: ''
}
},
computed: {
error () {
return this.username.trim().length < 7
? 'Please enter a longer username'
: ''
}
}
}
</script>
<style scoped>
</style>
TestDemo.spec.js的配置檔案
import Vue from "vue"
import { expect } from "chai"
import Hello from "@/components/TestDemo.vue"
describe("Hello", () => {
it("notice informaiton should be showd correctly",()=>{
// 正常的主程式是使用html來渲染username得到一個頁面,但是這裡既然是做測試,就是模拟html來渲染,使用的是Vue.extends來渲染的。
// 是以第一步是将Hello這個元件用Vue.extends來渲染。
const renderbynode=Vue.extends(Hello)
// 渲染完了按照道理就可以在渲染的結果中尋找自己檢查的東西了,但是為啥這裡還要有一個mount,不明白
const wrapper = new renderbynode().$mount()
//測試例1:給wrapper的輸入框中輸入7個空格,所期待的結果是在頁面中有一個error字段。
// `username` 在除去頭尾空格之後不應該少于 7 個字元
wrapper.setData({ username: ' '.repeat(7) })
// 确認錯誤資訊被渲染了,有錯誤資訊,結果是true
expect(wrapper.find('.error').exists()).to.not.be(false)
//測試例2:給wrapper的輸入框中輸入'Lachlan' ,所期待的結果是在頁面中沒有error字段。
//如果有error字段,則是錯誤的,沒有通過。
// 将名字更新至足夠長
wrapper.setData({ username: 'Lachlan' })
// 斷言錯誤資訊不再顯示了
expect(wrapper.find('.error').exists()).to.not.be(true)
})
})
問題1:xxx is not a function的問題。完全按照vue單元測試的官網寫就沒有問題。
問題2:it中定義的xxx.username=“123”,無效。也就是說無法更改頁面中username的初始設定。
需要加入一個factory function,用于模拟資料的輸入。
import { expect } from "chai"
import { shallowMount } from '@vue/test-utils'
import Hello from "@/components/TestDemo.vue"
const factory = (values = {}) => {
return shallowMount(Hello, {
data () {
return {
...values
}
}
})
}
describe("Hello", () => {
it("1-h1 title",()=>{
const vm = factory()
expect(vm.find('.hello').text()).equal("hello world")
}),
describe("2-input", () => {
it("2.1-input longer than 7",()=>{
const vm = factory({username:"12345678"})
expect(vm.find('.error').text()).equal("Correct enter")
}),
it("2.2-input shorter than 7",()=>{
const vm = factory({username:"123"})
expect(vm.find('.error').text()).equal('Please enter a longer username')
})
})
})