自從小程序自定義組件和 npm 功能面世之后,組件化和開源思想逐步開始萌芽了。我們可以將一些通用的部件,如自定義導(dǎo)航欄之類的封裝到一個(gè)自定義組件中,然后借由 npm 平臺(tái)開源出去給其他開發(fā)者使用,這樣可以省去很多勞動(dòng)。相信各位開發(fā)老爺們應(yīng)該或多或少都有過使用開源包的經(jīng)歷,但是在使用前,這個(gè)開源包得能贏取我們的信任,一個(gè)很重要的指標(biāo)就是單元測(cè)試通過率和覆蓋率。
但是因?yàn)樾〕绦颡?dú)特的運(yùn)行環(huán)境和不完全開源的基礎(chǔ)款,使得對(duì)小程序自定義組件的單元測(cè)試稍微有點(diǎn)困難。目前市面上無論是 vue 還是 react,這些組件化框架都有一套完善的單元測(cè)試解決方案,但是對(duì)于小程序自定義組件來說卻寥寥無幾,因此這個(gè)工具集—— miniprogram-simulate 便應(yīng)運(yùn)而生了。
閑話不多說,我們先看下小程序的運(yùn)行機(jī)制:

可以看出,小程序自定義組件是渲染與邏輯脫離,想在邏輯層拿到渲染的結(jié)果進(jìn)而進(jìn)行對(duì)比測(cè)試是很難辦到的。而且目前小程序的環(huán)境并不開放,想要完整構(gòu)造模擬出小程序的運(yùn)行環(huán)境也不太科學(xué)。另外我們這邊只是需要對(duì)小程序的自定義組件做單元測(cè)試,對(duì)于小程序中很多非自定義組件相關(guān)的功能可以不考慮,而且在性能上也不那么苛求,所以一個(gè)思路是調(diào)整底層運(yùn)行機(jī)制,將雙線程合并為一個(gè)線程,將 wxml、wxss 的解析器改成純 js 實(shí)現(xiàn)。
只是有思路還不夠,在實(shí)現(xiàn)過程中還是有一些坎的。比如要如何比較好的模擬出小程序自定義組件的各種特性和功能呢?自己實(shí)現(xiàn)也不是不行,問題在于維護(hù)的成本,如果小程序自定義組件實(shí)現(xiàn)了一個(gè)功能,測(cè)試工具還得更新一下。另外如果在實(shí)現(xiàn)上略有差池的話,可能小程序端的一個(gè)小調(diào)整對(duì)于測(cè)試工具都可能是傷筋動(dòng)骨式的改造。所以這里直接將小程序自定義組件的最核心模塊—— exparser 從基礎(chǔ)庫(kù)中抽離出來。
exparser 是自定義組件系統(tǒng)的內(nèi)核,是一個(gè)完整獨(dú)立的模塊,不依賴于基礎(chǔ)庫(kù)中其他模塊。它完全脫離于小程序的 api 和運(yùn)行機(jī)制體系,所以無論是單線程還是雙線程機(jī)制都可以使用。exparser 提供的是自定義組件系統(tǒng)最底層的接口,測(cè)試工具將其進(jìn)行二次封裝成自定義組件測(cè)試環(huán)境。如果基礎(chǔ)庫(kù)有關(guān)于自定義組件的更新,如果是底層改造,則直接更新 exparser 模塊即可;如果只是外層改造,那基本上是暴露接口層面的調(diào)整,也不必作太多大范圍的調(diào)整。
PS:目前雖然 exparser 已經(jīng)發(fā)布到 npm,但是仍然只是混淆壓縮后到代碼,屬于半開源狀態(tài),不建議開發(fā)者直接使用。
miniprogram-simulate 本是自定義組件腳手架 miniprogram-custom-component 中的一部分,現(xiàn)單獨(dú)抽離出來,方便開發(fā)者們作更多的使用選擇(腳手架中默認(rèn)使用 jest 來搭配使用,直接使用此工具集則可以搭配其他想要使用的測(cè)試框架,比如 mocha 等)。
下述只簡(jiǎn)單介紹下用法,首先安裝此工具集:
npm install --save-dev miniprogram-simulate 復(fù)制代碼
然后此工具集必須搭配其他測(cè)試框架和 jsdom 來使用,比如 jest。因?yàn)?jest 內(nèi)置有 jsdom,所以也就不需要額外安裝 jsdom 了,以下面一個(gè)自定義組件作為例子:
<!-- 自定義組件:comp.wxml -->
<view class="index">{{prop}}</view>
/* 自定義組件:comp.wxss */
.index {
color: green;
}
// 自定義組件 comp.js
Component({
properties: {
prop: {
type: String,
value: 'index.properties'
},
},
})
|
這是一個(gè)極其簡(jiǎn)單的自定義組件,之后我們便可開始在 comp.test.js 里編寫測(cè)試用例。
加載和渲染自定義組是最基礎(chǔ)的功能:
// 自定義組件 comp 的測(cè)試用例:comp.test.js
const path = require('path')
const simulate = require('miniprogram-simulate')
test('comp', () => {
const id = simulate.load(path.join(__dirname, './comp')) // 此處必須傳入絕對(duì)路徑
const comp = simulate.render(id) // 渲染成自定義組件樹實(shí)例
const parent = document.createElement('parent-wrapper') // 創(chuàng)建父親節(jié)點(diǎn)
comp.attach(parent) // attach 到父親節(jié)點(diǎn)上,此時(shí)會(huì)觸發(fā)自定義組件的 attached 鉤子
expect(comp.querySelector('.index').dom.innerHTML).toBe('index.properties') // 測(cè)試渲染結(jié)果
// 執(zhí)行其他的一些測(cè)試邏輯
comp.detach() // 將組件從父親節(jié)點(diǎn)中移除,此時(shí)會(huì)觸發(fā)自定義組件的 detached 生命周期
})
|
可以獲取自定義組件的數(shù)據(jù):
test('comp', () => {
// 前略
// 判斷組件數(shù)據(jù)
expect(comp.data).toEqual({
a: 111,
})
})
|
可以更新自定義組件的數(shù)據(jù):
test('comp', () => {
// 前略
// 更新組件數(shù)據(jù)
comp.setData({
a: 123,
})
})
|
可以獲取自定義組件的子組件:
test('comp', () => {
// 前略
const childComp = comp.querySelector('#child-id')
expect(childComp.dom.innerHTML).toBe('<div>child</div>')
})
|
可以模擬觸發(fā)自定義組件的事件:
test('comp', () => {
// 前略
comp.dispatchEvent('touchstart') // 觸發(fā)組件的 touchstart 事件
childComp.dispatchEvent('tap') // 觸發(fā)子組件的 tap 事件
})
|
至此,應(yīng)該能大概了解到這個(gè)工具集的用途。這些只是簡(jiǎn)單的使用介紹,本文只是個(gè)引子,更多詳細(xì)的用法請(qǐng)移步到 github 倉(cāng)庫(kù)上查閱。
要想判斷一個(gè)自定義組件的質(zhì)量如何,其中最簡(jiǎn)單的方法就是看單元測(cè)試的表現(xiàn),想要?jiǎng)e人使用你的自定義組件,質(zhì)量把關(guān)很重要,目前 miniprogram-simulate 已經(jīng)實(shí)現(xiàn)了最基本的功能,其他功能也在盡力施工中,有什么好的建議或者使用上遇到什么問題也可以提 issue。如果好評(píng)請(qǐng) star 噢~