吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】


吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】


前言

據上節文章《 》發佈已經有了兩個星期了。期間收到了 1000+ 個贊,30000+ 閱讀量,這是我萬萬沒想到的。自己的文章能有這麼高的關注度,真的很令人滿意!

但是相反,寫文章的壓力更加大了。一篇文章總是反反覆覆的修改,總是擔心自己的認知水平技術水平不夠,甚至導致有些地方會誤導讀者。所以寫文章更加費時間了。

也會揣摩自己寫作風格有沒有什麼問題、會不會太囉嗦、哪些地方沒講清楚等等...如果有不好的地方可以評論指出來,接受批評,批評也是一種進步的動力!

常規操作,先點贊後觀看哦!你的點贊是我創作的動力之一!

全系列概覽

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

問題

這節我將從 5 個方面來論述 vue 開發過程中的一些技巧和原理。如果你還未觀看上節文章,可以移步至16個方面深入前端工程化開發技巧《上》觀看。

本節內容主要圍繞下列問題展開:

  • 如何編寫原生組件,以及組件編寫的思考與原則?
  • 組件怎麼通信?有哪些方式?
  • 如何使用vuex 以及它的應用場景和原理
  • 如何使用過濾器,編寫自己的過濾器
  • 如何使用 Jest 測試你的代碼?TDD 與 BDD 的比較

實踐

實踐之前:我希望你有如下準備,或者知識儲備。

瞭解 npm/yarn/git/sass/pug/vue/vuex/vue-router/axios/mock/ssr/jest 的使用和原理。

當然上面知識不瞭解也沒關係哈哈哈,文章中會提到大致用法和作用。

如何編寫原生組件

組件編寫原理

vue 編寫組件有兩種方式,一種是單文件組件,另外一種函數組件。根據組件引入和創建還可以分為動態組件異步組件

動態組件keep-alive使之緩存。異步組件原理和異步路由一樣,使用import()實現異步加載也就是按需加載。

所謂 vue 單文件組件,就是我們最常見的這種形式:

<code><template> 

\t.demo
\th1 hello
/<template>

<style><br> .demo {<br> h1 { color: #f00; }<br> }<br>/<style>
複製代碼/<code>

這裡的template 也可以使用 render 函數來編寫

<code>Vue.component('demo', {
render: function (createElement) {
return createElement(
'h1',
\t 'hello',
// ...
)
}
})
複製代碼/<code>

我們可以發現render函數寫模版讓我們更有編程的感覺。對模版也可以編程,在vue 裡面我們可以很容易聯想到,很多地方都有兩種寫法,一種是 template , 一種是js。

比如:對於路由,我們既可以使用:to="",又可以使用$router.push,這也許是 vue 用起來比較爽的原因。

函數式組件是什麼呢?

functional,這意味它無狀態 (沒有響應式數據),也沒有實例 (沒有 this 上下文)

  • 單文件形式 2.5.0+
<code><template>
/<template>
複製代碼/<code>
  • Render 函數形式
<code>Vue.component('my-component', {
functional: true,
render function (createElement, context) {
\treturn createElement('div')
\t}
}
複製代碼/<code>

為什麼要使用函數組件呢?

最重要的原因就是函數組件開銷低,也就是對性能有好處,在不需要響應式和this的情況下,寫成函數式組件算是一種優化方案。

組件寫好了,需要將組件註冊才能使用

組件註冊的兩種方式

組件註冊分為兩種,一種是全局註冊,一種是局部註冊

局部註冊就是我們常用的 Vue.component('s-button', { /* ... */ }),比較簡單不詳細論述

全局註冊上節已經提到,在new Vue 之前在 mian.js 註冊,這裡還提到一種自動全局註冊的方法 require.text

<code>import Vue from 'vue'
import upperFirst from 'lodash/upperFirst'
import camelCase from 'lodash/camelCase'

const requireComponent = require.context(
'./components',
// 是否查詢其子目錄
false,
/Base[A-Z]\\w+\\.(vue|js)$/
)
requireComponent.keys().forEach(fileName => {
// 獲取組件配置
const componentConfig = requireComponent(fileName)
const componentName = upperFirst(
camelCase(
// 獲取和目錄深度無關的文件名
fileName
.split('/')
.pop()
.replace(/\\.\\w+$/, '')
)
)
// 全局註冊組件
Vue.component(
componentName,
componentConfig.default || componentConfig
)
})
複製代碼/<code>

基本原理和全局註冊一樣,就是將 components 中的組件文件名,appButton 變成 AppButton 作為註冊的組件名。把原來需要手動複製的,變成之間使用 keys 方法批量註冊。

實踐開發一個 button 組件

現在,我們以寫一個簡單的原生button組件為例,探討一下組件開發的一些關鍵點。 寫之前,我們需要抓住 4 個核心的要點:

  • 用哪個標籤作為組件主體,button 還是 div 標籤
  • 如何根據屬性控制 button 組件的顏色 color、形狀 type、大小 size
  • 如何處理 button 組件的點擊事件
  • 如何去擴展 button 組件的內容

這些思考點在其他原生組件開發和高階組件封裝裡面也需要考慮到

首先看第一個問題,大部分原生組件第一考慮的地方,就是主要標籤用原生<button>標籤還是用

去模擬。

為什麼不考慮 呢,因為 <button> 元素比 元素更容易添加內部元素。你可以在元素內添加HTML內容(像 甚至

),以及 ::after 和 ::before 偽元素來實現複雜的效果,而 只支持文本內容。/<button>

下面分析這兩種寫法的優劣

使用原生button標籤的優勢:

  1. 更好的標籤語義化
  2. 原生標籤自帶的 buff,一些自帶的鍵盤事件行為等

為什麼說更好的語義化呢?有人可能會說,可以使用 role 來增強 div 的語義化。確實可以,但是可能存在問題——有些爬蟲並不會根據 role 來確定這個標籤的含義。

另外一點,對開發者來說,<button> 比

閱讀起來更好。

使用 div 模擬的優勢:

  1. 不需要關心button原生樣式帶來的一些干擾,少寫一些覆蓋原生 css 的代碼,更乾淨純粹。
  2. 全部用 div ,不需要再去找原生標籤、深入瞭解原生標籤的一些兼容相關的詭異問題。
  3. 可塑性更強,也就是拓展性和兼容性更好。這也是大多數組件都會選擇使用 div 作為組件主體的原因。

貌似 div 除了語義不是很好以外,其他方面都還行,但是具體用哪一種其實都可以,只要代碼寫的健壯適配性強,基本都沒啥大問題。

我們這裡使用原生<button>作為主要標籤,使用s-xx作為class前綴

為什麼需要使用前綴,因為在有些時候,比如使用第三方組件,多個組件之間的 class 可能會產生衝突,前綴用來充當組件 css 的一個命名空間,不同組件之間不會干擾。

<code><template>
button.s-button(:class="xxx" :style="xxx" )
slot
/<template>
複製代碼/<code>

然後,我們看第二個問題:

如何根據屬性來控制 button 的樣式 其實這個很簡單,基本原理就是:

  1. 使用 props 獲取父組件傳過來的屬性。
  2. 根據相關屬性,生成不同的class,使用 :class="{xxx: true, xxx: 's-button--' + size}" 這種形式,在 style 裡面對不同的s-button--xxx 做出不同的處理。
<code><template>
button.s-button(:class="" :style="" )
slot
/<template>


複製代碼/<code>

如何使用事件以及如何擴展組件

擴展組件的原理,主要就是使用 props 控制組件屬性,模版中使用 v-if/v-show 增加組件功,比如增加內部 ICON,使用:style``class控制組件樣式。

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

還要注意的是我們還要控制原生 type 類型,原生默認是submit,這裡我們默認設置為button

<code><template>
button.s-button(:class="" :style="" :type="nativeType")
slot
s-icon(v-if="icon && $slots.icon" name="loading")
/<template>

複製代碼/<code>

控制事件,直接使用 @click="" + emit

<code><template>
button.s-button(@click="handleClick")
/<template>

複製代碼/<code>

最後總結下:

一般就直接使用template單文件編寫組件,需要增強 js編寫能力可以使用render()

常規編寫組件需要考慮:1. 使用什麼標籤 2. 如何控制各種屬性的表現 3. 如何增強組件擴展性 4. 如何處理組件事件

對響應式this要求不高,使用函數functional組件優化性能。

基礎組件通常全局註冊,業務組件通常局部註冊

使用keys()遍歷文件來實現自動批量全局註冊

使用import() 異步加載組件提升減少首次加載開銷,使用keep-alive + is緩存組件減少二次加載開銷

如何使用 vuex 以及它的應用

由來以及原理

我們知道組件中通信有以下幾種方式:

1. 父組件通過 props 傳遞給子組件,不詳細論述

2. 子組件通過 emit 事件傳遞數據給父組件,父組件通過 on 監聽,也就是一個典型的訂閱-發佈模型

@ 為 v-on: 的簡寫

<code><template>

div.component-1
<template>
export default {
mounted() {
this.$emit('eventName', params)
}
}

複製代碼/<template>/<template>/<code>
<code>
<template>
Component-1(@eventName="handleEventName")
<template>

複製代碼/<template>/<template>/<code>

3. 集中式通信事件,主要用於簡單的非父子組件通信

原理很簡單其實就是在 emit 與 on 的基礎上加了一個事件中轉站 “bus”。我覺得更像是現實生活中的集線器。

普遍的實現原理大概是這樣的 “bus” 為 vue 的一個實例,實例裡面可以調用emit,off,on 這些方法。

<code>var eventHub = new Vue()

// 發佈
eventHub.$emit('add', params)
// 訂閱/響應
eventHub.$on('add', params)
// 銷燬
eventHub.$off('add', params)
複製代碼/<code>

但是稍微複雜點的情況,使用這種方式就太繁鎖了。還是使用 vuex 比較好。

從某種意義而言,我覺得 vuex 不僅僅是它的一種進化版。

  1. 使用store作為狀態管理的倉庫,並且引入了狀態這個概念
  2. 它的模型完全不一樣了,bus 模型感覺像一個電話中轉站
吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

而 vuex 的模型更像是集中式的代碼倉庫。

與 git 類似,它不能直接修改代碼,需要參與者提交 commit,提交完的 commit修改倉庫,倉庫更新,參與者 fetch 代碼更新自己的代碼。不同的是代碼倉庫需要合併,而 vuex 是直接覆蓋之前的狀態。

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

管理 vuex

原理

“store”基本上就是一個容器,它包含著你的應用中大部分的狀態 (state)。Vuex 和單純的全局對象有以下兩點不同

  • 響應式(改變屬性後,觸發組件中的狀態改變,觸發 dom)
  • 不能直接改變狀態(唯一途徑是提交 mutation)
吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

基本用法:就是在 state 裡面定義各種屬性,頁面或組件組件中,直接使用 $store.state或者$store.getters來使用。如果想要改變狀態state呢,就commit 一個mutation

但是假如我想提交一連串動作呢?可以定義一個action,然後使用 $store.dispatch 執行這個 action

使用action 不僅能省略不少代碼,而且關鍵是action 中可以使用異步相關函數,還可以直接返回一個promise

而為什麼不直接到mutation中寫異步呢? 因為mutation 一定是個同步,它是唯一能改變 state 的,一旦提交了 mutation,mutation 必須給定一個明確結果。否則會阻塞狀態的變化。

下面給出常用 vuex 的使用方式

新建 Store

新建一個store並將其他各個功能化分文件管理

<code>import Vue from 'vue'
import Vuex from 'vuex'
import state from './states'
import getters from './getters'
import mutations from './mutations'
import actions from './actions'
import user from './modules/user'
Vue.use(Vuex)
export default new Vuex.Store({
//在非生產環境下,使用嚴格模式

strict: process.env.NODE_ENV !== 'production',
state,
getters,
mutations,
actions,
modules: {
user
}
})
複製代碼/<code>

操作狀態兩種方式

  1. 獲取狀態
<code>console.log(store.state.count)
複製代碼/<code>
  1. 改變狀態
<code>store.commit('xxx')
複製代碼/<code>

管理狀態 states

單一狀態樹, 這也意味著,每個應用將僅僅包含一個 store 實例。單一狀態樹讓我們能夠直接地定位任一特定的狀態片段,在調試的過程中也能輕易地取得整個當前應用狀態的快照。

<code>// states 文件
export default {
count: 0
}
複製代碼/<code>

計算屬性中返回,每當 state 中屬性變化的時候, 其他組件都會重新求取計算屬性,並且觸發更新相關聯的 DOM

<code>const Counter = {
\ttemplate: '
{{count}}
',
\tcomputed: {
\t\tcount() {
\t\t\treturn store.state.count
\t\t}
\t}
}
複製代碼

管理取值器 getters

getters 相當於 store 的計算屬性。不需要每次都要在計算屬性中過濾一下,也是一種代碼複用。 我們在getters文件中管理

<code>export default {
count: (state) => Math.floor(state.count)
}
複製代碼/<code>

管理 mutations

更改 Vuex 的 store 中的狀態的唯一方法是提交 mutation。Vuex 中的 mutation 非常類似於事件:每個 mutation 都有一個字符串的 事件類型 (type) 和 一個 回調函數 (handler)。這個回調函數就是我們實際進行狀態更改的地方

使用 types 大寫用於調試,在mutationTypes 文件中export const ROUTE_ADD = 'ROUTE_ADD'

然後在mutations 文件中管理

<code>import * as MutationTypes from './mutationTypes' 

export default {
[MutationTypes.ADDONE]: function(state) {
state.count = state.count + 1
},
//...
}
複製代碼/<code>
<code>this.$store.commit(MutationTypes.ADDONE)
複製代碼/<code>

管理 actions

和 mutations 類似,actions 對應的是dispatch,不同的是action可以使用異步函數,有種更高一級的封裝。

<code>// 簡化
actions: {
increment ({ commit }) {
setTimeout(() => {
commit(MutationTypes.ADDONE)
}, 1000)
}
}
// 觸發
store.dispatch('increment')
複製代碼/<code>

上述用法都可以使用載荷的形式,引入也可以使用 mapXxxx 進行批量引入,這裡不詳細論述,有興趣可以查看官網。

分模塊管理狀態

狀態太多太雜,分模塊管理是一個良好的代碼組織方式。

<code>import count from './modules/count'
export default new Vuex.Store({
modules: {

count
}
})
複製代碼/<code>

每一個模塊都可以有獨立的相關屬性

<code>import * as ActionTypes from './../actionTypes'
export default {
state: {
count: 0
},
mutations: {
ADD_ONE: function(state) {
state.count = state.count + 1
}
},
actions: {
[ActionTypes.INIT_INTENT_ORDER]: function({ commit }) {
commit('ADD_ONE')
}
},
getters: {
pageBackToLoan: (state) => Math.floor(state.count)
}
}
複製代碼/<code>
吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

應用場景

vuex 主要有幾個應用場景,一個是用於組件通信狀態共享,一個是用於數據緩存,還有就是用於減少請求。這些場景歸根節點都是對於緩存共享來說的。

組件通信

首先,狀態統一管理在倉庫,就實現了組件通信的可能性。

當一個組件通過 commit 提交 mutation 就改了 state,其他組件就可以通過store.state獲取最新的state,這樣一來就相當於更新的值通過 store 傳遞給了其他組件,不僅實現了一對一的通信,還實現了一對多的通信。

狀態共享

我們經常使用的一個場景就是權限管理

寫權限管理時候,首次進入頁面就要將權限全部拿到,然後需要分發給各個頁面使用,來控制各個路由、按鈕的權限。

<code>/**
* 判斷用戶有沒有權限訪問頁面
*/
function hasPermission(routeItem, menus) {
return menus.some(menuItem => {
return menuItem.name === routeItem.name
})
}

/**
* 遞歸過濾異步路由表,返回符合用戶角色權限的路由表
* @param {*} routes 路由表
* @param {*} menus 菜單信息
*/
function filterRoutes(routes, menus) {
return routes.filter(routeItem => {
if (routeItem.hidden) {
return true
} else if (hasPermission(routeItem, menus)) {
const menuItem = menus.find(item => item.name === routeItem.name)
if (menuItem && menuItem.children && menuItem.children.length > 0) {
routeItem.children = filterRoutes(routeItem.children, menuItem.children)
if (!routeItem.children.length) return false
}
return true
} else {
return false
}
})
}
const permission = {
state: {
routers: constantRouterMap,
addRouters: [],
roles: [],
user_name: '',
avatar_url: '',
onlyEditor: false,

menus: null,
personal: true,
teamList: []
},
mutations: {}
}
export default permission
複製代碼/<code>

而且權限還可以被更改,更改後的權限直接分發到其他頁面組件中。這個場景要是不使用 vuex ,代碼將會比較複雜。

數據緩存

store 是一個倉庫,它從創建開始就一直存在,只有頁面 Vuex.store 實例被銷燬,state 才會被清空。具體表現就是刷新頁面。

這個數據緩存適用於:頁面加載後緩存數據,刷新頁面請求數據的場景。在一般Hybrid中,一般不存在刷新頁面這個按鈕,所以使用 vuex 緩存數據可以應對大多數場景。

<code>export default {
state: {
// 緩存修改手機號需要的信息
changePhoneInfo: {
nameUser: '',
email: '',
phone: ''
},
}
}
複製代碼/<code>

如果需要持久化緩存,結合瀏覽器或 APP 緩存更佳。

<code>export default {
// 登陸成功後,vuex 寫入token,並寫入app緩存,存儲持久化
[ActionTypes.LOGIN_SUCCESS]: function(store, token) {
store.commit(MutationTypes.SET_TOKEN, token)
setStorage('token', token)
router.replace({ name: 'Home', params: { source: 'login' } })
}
}
複製代碼/<code>

減少請求(數據緩存的變種)

在寫後臺管理平臺時候,經常會有 list 選型組件,裡面數據從服務端拿的數據。如果我們把這個 list 數據存儲起來,下次再次使用,直接從 store 裡面拿,這樣我們就不用再去請求數據了。相當於減少了一次請求。

如何使用過濾器,編寫自己的過濾器

原理

假設我現在有個需求,需要將性別0、1、2,分別轉換成男、女、不確定這三個漢字展示。頁面中多處地方需要使用。

<code><template>
.user-info
.gender
label(for="性別") 性別
span {{gender}}
/<template>
複製代碼/<code>

完成這個需求,我們知道有 4 種方式:

  1. 使用 computed 方法
  2. 使用 methods 方法
  3. 使用 utils 工具類
  4. 使用 filters

應該選擇哪種方式呢?

我從下面三個方面來論述這個問題

1. 可實現性

  • 使用 computed 實現成功,我們知道computed不是一個函數是無法傳參的,這裡有個技巧,return 一個函數接受傳過來的參數
<code>  // ...
computed: {
convertIdToName() {
return function(value) {
const item = list.find(function(item) {
return item.id === value
})
return item.name
}
}
}
複製代碼/<code>
吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

  • 使用 methods 實現成功,這裡直接可以傳參數,一種常規的寫法。

注意 methods、computed和 data 相互之前是不能同名的

<code>  // ...
methods: {
convertIdToName(value) {
const item = list.find(function(item) {
return item.id === value
})
return item.name
}

}
複製代碼/<code>
  • 使用 utils 和 methods 差不多基本上也可以實現
  • 使用 filter 也是實現的,有個可以和methods、computed同名哦
<code>  filters: {
console.log(this.render)
convertIdToName(value) {
const item = list.find(function(item) {
return item.id === value
})
return item.name
}
},
複製代碼/<code>

總的來說他們全部都可以實現這個需求

2. 侷限性

  • computed 、methods 和data 三者互不同名,他們沒辦法被其他組件使用,除非通過 mixins
  • filters 與 utils 無法訪問 this ,也就是於響應式絕緣。但是通過定義全局filters,可以其他地方使用,另外還可以直接加載第三方filter和utils

3. 總結比較

filters 與 utils 歸屬一對,他們既是脫離了 this,獲得了自由,又是被this 棄之門外。相反 methods 與 computed 與 this 緊緊站在一起,但又是無法獲得自由。

例子

編寫一個簡單的千分位過濾器

<code>export const thousandBitSeparator = (value) => {
return value && (value
.toString().indexOf('.') !== -1 ? value.toString().replace(/(\\d)(?=(\\d{3})+\\.)/g, function($0, $1) {
return $1 + ',';
}) : value.toString().replace(/(\\d)(?=(\\d{3})+$)/g, function($0, $1) {
return $1 + ',';
}));
}
複製代碼/<code>

使用 vue-filter 插件

兩款插件

vue-filter:https://www.npmjs.com/package/vue-filter 使用 use 引入

vue2-filters:https://www.npmjs.com/package/vue-filters 使用mixins 引入

有需要的話,我一般就用第二個了,大多數都是自己寫一下小過濾器

自定義過濾器之後,直接全局自動註冊,其他地方都可以使用

註冊全局過濾器

遍歷過濾屬性值,一次性全部註冊

<code>for (const key in filters) { 

Vue.filter(key, filters[key])
}
複製代碼/<code>

如何使用 Jest 測試你的代碼

原理

我們思考一下測試 js 代碼需要哪些東西

  1. 瀏覽器運行環境
  2. 斷言庫

如果是測試 vue 代碼呢? 那得再加一個 vue 測試容器

Jest + Vue

安裝依賴

<code>{
"@vue/cli-plugin-unit-jest": "^4.0.5",
"@vue/test-utils": "1.0.0-beta.29",
"jest": "^24.9.0",
// ...
}
複製代碼/<code>

項目配置

<code>// For a detailed explanation regarding each configuration property, visit:
// https://jestjs.io/docs/en/configuration.html

module.exports = {
preset: '@vue/cli-plugin-unit-jest',
automock: false,
"/private/var/folders/10/bb2hb93j34999j9cqr587ts80000gn/T/jest_dx",
clearMocks: true,
// collectCoverageFrom: null,

coverageDirectory: 'tests/coverage'
//...
}
複製代碼/<code>

單元測試

測試 utils 工具類

對我們之前寫的一個性別名稱轉換工具進行測試

<code>import { convertIdToName } from './convertIdToName'
describe('測試convertIdToName方法', () => {
const list = [
{ id: 0, name: '男' },
{ id: 1, name: '女' },
{ id: 2, name: '未知' }
]
it('測試正常輸入', () => {
const usage = list
usage.forEach((item) => {
expect(convertIdToName(item.id, list)).toBe(item.name)
})
})
it('測試非正常輸入', () => {
const usage = ['a', null, undefined, NaN]
usage.forEach((item) => {
expect(convertIdToName(item, list)).toBe('')
})
})
})
複製代碼/<code>

這樣一測試,發現原來我們之前寫的工具有這麼多漏洞

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

測試正常輸入全部通過了,非正常輸入失敗了,根據測試用例改進我們的代碼

<code>export const convertIdToName = (value, list) => {
if (value !== 0 && value !== 1 && value !== 2) return ''
const item = list.find(function(item) {
return item.id === value
})
return item.name
}
複製代碼/<code>

現在測試都通過了呢

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

測試 components 單文件組件

對我們最簡單的 hello world 進行測試

<code><template>
.hello
h1 {{ msg }}
/<template>


複製代碼/<code>
<code>import { shallowMount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
it('renders props.msg when passed', () => {
const msg = 'new message'
const wrapper = shallowMount(HelloWorld, {
propsData: { msg }
})
expect(wrapper.text()).toMatch(msg)
})
})
複製代碼/<code>

測試 api 請求

異步測試有幾種常見寫法

  • async 與 await
  • done()

簡單的異步測試,測試一個簡單的登陸請求

<code>export const login = (data) => post('/user/login', data)
複製代碼/<code>

測試代碼

<code>import { login } from '@/api/index'
describe('login api', () => {
const response = {
code: '1000',
data: {}
}
const errorResponse = {
code: '5000',
data: {},
message: '用戶名或密碼錯誤'
}
it('測試正常登陸', async () => {
const params = {
user: 'admin',
password: '123456'
}
expect(await login(params)).toEqual(response)
})
it('測試異常登陸', async () => {
const params = {
user: 'admin',
password: '123123'
}
expect(await login(params)).toEqual(errorResponse)
})
})
複製代碼/<code>
吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

功能模塊測試

組件,api,工具這些零零碎碎都測試了,而且這些都是比較通用、和業務關係不大的代碼,它們改動較少,測試到這裡其實已經足夠了,已經達到了 20% 的測試工作量了很大一部分代碼的目的。

為什麼我說只有 20% 的工作量呢?因為這些都是不怎麼變化的邏輯,是一勞永逸的事情。長遠來說佔用的工作量確實很少。

但是有些情況業務還是必須得測,也就是必須要功能模塊集成測試。

經常得迴歸的業務,那種迭代對原有的系統比較大,避免改動之後使舊的代碼各種新的問題。這種經常回歸測試,採用 BDD + 集成測試,比不停改 bug 要輕鬆的多。

快照測試

像版本一樣,每次測試之後生成一個版本,比較與上一個版本的區別。 這是一種粒度及其小的測試,可以測試到每一個符號。

比如我用它來測試一個配置文件的變動

這是我們一個配置文件

<code>export const api = {
develop: 'http://xxxx:8080',
mock: 'http://xxxx',
feature: 'http://xxxx',
test: 'http://xxxx',
production: 'http://xxxx'
}
export default api[process.env.NODE_ENV || 'dev']
複製代碼/<code>

使用快照測試

<code>import { api } from './config'

describe('配置文件測試', () => {
it('測試配置文件是否變動', () => {
expect(api).toMatchSnapshot({
develop: 'http://xxxx:8080',
mock: 'http://xxxx',
feature: 'http://xxxx',
test: 'http://xxxx',
production: 'http://xxxx'
})
})
})

複製代碼/<code>

使用快照第一次測試後,通過測試,代碼被寫入快照

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

改動配置再次測試,未通過測試

吃透 Vue 項目開發實踐|16個方面深入前端工程化開發技巧【中】

這時如果要改變配置怎麼辦呢? 只需同時改一下用例就好了。快照將再次寫入快照生成版本2,這樣配置改動也有根據了

TDD 與 BDD

最近討論比較多的算是測試驅動開發行為驅動開發,其實總得來說是 4 種

  1. 不寫測試。好處是省時間,壞處當然就 bug 多,代碼質量低。
  2. 先寫測試和測試用例,再寫代碼,也就是測試驅動開發。這樣好處是代碼比較健全,考慮的因素比較多。固定模塊健壯性高,bug少。
  3. 先寫代碼,再通過模擬用戶的行為寫測試。好處是思路清晰,如果測試用例夠全面,基本上線基本沒太大問題,迴歸測試也很好做。
  4. 寫代碼之前寫測試和用例,寫完代碼之後再寫用戶行為測試。這種代碼質量就太高了,當然缺點就是費時間。

那麼你是哪一種? 反正我比較佛系哈,有的不寫測試,也有的寫滿測試。

總結

本篇文章耗費作者一個多星期的業餘時間,存手工敲打 6000+字,同時收集,整理之前很多技巧和邊寫作邊思考總結。如果能對你有幫助,便是它最大的價值。都看到這裡還不點贊,太過不去啦!

由於技術水平有限,文章中如有錯誤地方,請在評論區指出,感謝!

文中大多數代碼將在suo-design-pro 中更新

"
/<code>


分享到:


相關文章: