吃透 Vue 項目開發實踐|16個方面深刻前端工程化開發技巧《中》

前言

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

可是相反,寫文章的壓力更加大了。一篇文章老是反反覆覆的修改,老是擔憂本身的認知水平技術水平不夠,甚至致使有些地方會誤導讀者。css

也會揣摩本身寫做風格有沒有什麼問題、會不會太囉嗦、哪些地方沒講清楚等等...若是有很差的地方能夠評論指出來,接受批評,批評也是一種進步的動力!html

原本準備上下兩節寫徹底部內容,發現實際不太可能,還沒寫完 4 章,就已經 6000—+ 字了。最後一節寫完以後就準備回家過年了,這裏提早祝你們新年快樂!前端

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

全系列概覽

問題

這節我將從 5 個方面來論述 vue 開發過程當中的一些技巧和原理。若是你還未觀看上節文章,能夠移步至16個方面深刻前端工程化開發技巧《上》觀看。java

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

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

實踐

實踐以前:我但願你有以下準備,或者知識儲備。git

  • 瞭解 npm/yarn/git/sass/pug/vue/vuex/vue-router/axios/mock/ssr/jest 的使用和原理。
  • 固然上面知識不瞭解也不要緊哈哈哈,文章中會提到大體用法和做用。

如何編寫原生組件

組件編寫原理

vue 編寫組件有兩種方式,一種是單文件組件,另一種函數組件。根據組件引入和建立還能夠分爲動態組件異步組件github

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

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

<template lang="pug">
	.demo
  	h1 hello
</template>
<script> export default { name: 'demo', data() { return {} } } </script>
<style lang="scss" scoped> .demo { h1 { color: #f00; } } </style>
複製代碼

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

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

咱們能夠發現render函數寫模版讓咱們更有編程的感受。對模版也能夠編程,在vue 裏面咱們能夠很容易聯想到,不少地方都有兩種寫法,一種是 template , 一種是js

好比:對於路由,咱們既可使用:to="",又可使用$router.push,這也許是 vue 用起來比較爽的緣由。

函數式組件是什麼呢?

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

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

爲何要使用函數組件呢?

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

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

組件註冊的兩種方式

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

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

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

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
  )
})
複製代碼

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

實踐開發一個 button 組件

如今,咱們以寫一個簡單的原生button組件爲例,探討一下組件開發的一些關鍵點。 寫以前,咱們須要抓住 4 個核心的要點:

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

這些思考點在其餘原生組件開發和高階組件封裝裏面也須要考慮到

首先看第一個問題,大部分原生組件第一考慮的地方,就是主要標籤用原生<button></button>標籤仍是用<div></div> 去模擬。

爲何不考慮 <input/>呢,由於 <button> 元素比 <input> 元素更容易添加內部元素。你能夠在元素內添加HTML內容(像<em><strong> 甚至 <img>),以及 ::after::before 僞元素來實現複雜的效果,而 <input> 只支持文本內容。

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

使用原生button標籤的優點:

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

爲何說更好的語義化呢?有人可能會說,可使用 role 來加強 div 的語義化。確實能夠,可是可能存在問題——有些爬蟲並不會根據 role 來肯定這個標籤的含義。

另一點,對開發者來講,<button></button><div role="button"></div>閱讀起來更好。

使用 div 模擬的優點:

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

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

咱們這裏使用原生<button></button>做爲主要標籤,使用s-xx做爲class前綴

爲何須要使用前綴,由於在有些時候,好比使用第三方組件,多個組件之間的 class 可能會產生衝突,前綴用來充當組件 css 的一個命名空間,不一樣組件之間不會干擾。

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

而後,咱們看第二個問題:

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

  1. 使用 props 獲取父組件傳過來的屬性。

  2. 根據相關屬性,生成不一樣的class,使用 :class="{xxx: true, xxx: 's-button--' + size}" 這種形式,在 style 裏面對不一樣的s-button--xxx 作出不一樣的處理。

<template lang="pug">
  button.s-button(:class="" :style="" )
    slot
</template>
<script> export default { name: 's-button' data: return {} props: { theme: {}, size: {}, type: {} } } </script>
  
複製代碼

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

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

還要注意的是咱們還要控制原生 type 類型,原生默認是 submit,這裏咱們默認設置爲 button

<template lang="pug">
 button.s-button(:class="" :style="" :type="nativeType")
   slot
   s-icon(v-if="icon && $slots.icon" name="loading")
</template>
<script> export default { name: 's-button' data: return {} props: { nativeType: { type: String, default: 'button' }, theme: {}, size: {}, type: {} } } </script>
複製代碼

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

<template lang="pug">
  button.s-button(@click="handleClick")
</template>
<script> export default { methods: { handleClick (evt) { this.$emit('click', evt) } } } </script>
複製代碼

最後總結下:

通常就直接使用template單文件編寫組件,須要加強 js編寫能力可使用render()

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

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

基礎組件一般全局註冊,業務組件一般局部註冊

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

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

如何使用 vuex 以及它的應用

由來以及原理

咱們知道組件中通訊有如下幾種方式:

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

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

@v-on: 的簡寫

<template lang="pug">
<!--子組件-->
    div.component-1
<template>
export default {
  mounted() {
    this.$emit('eventName', params)
  }
}
</script>
複製代碼
<!-- 父組件-->
<template lang="pug">
    Component-1(@eventName="handleEventName")
<template>
<script> export default { methods: { handleEventName (params) { console.log(params) } } } </script>
複製代碼

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

原理很簡單其實就是在 emiton 的基礎上加了一個事件中轉站 「bus」。我以爲更像是現實生活中的集線器。

廣泛的實現原理大概是這樣的 「bus」 爲 vue 的一個實例,實例裏面能夠調用emit,off,on 這些方法。

var eventHub = new Vue()

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

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

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

  1. 使用store做爲狀態管理的倉庫,而且引入了狀態這個概念
  2. 它的模型徹底不同了,bus 模型感受像一個電話中轉站

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

git 相似,它不能直接修改代碼,須要參與者提交 commit,提交完的 commit修改倉庫,倉庫更新,參與者 fetch 代碼更新本身的代碼。不一樣的是代碼倉庫須要合併,而 vuex 是直接覆蓋以前的狀態。

管理 vuex

原理

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

  • 響應式(改變屬性後,觸發組件中的狀態改變,觸發 dom
  • 不能直接改變狀態(惟一途徑是提交 mutation)

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

可是假如我想提交一連串動做呢?能夠定義一個action,而後使用 $store.dispatch 執行這個 action

使用action 不只能省略很多代碼,並且關鍵是action 中可使用異步相關函數,還能夠直接返回一個promise

而爲何不直接到mutation中寫異步呢? 由於mutation 必定是個同步,它是惟一能改變 state 的,一旦提交了 mutationmutation 必須給定一個明確結果。不然會阻塞狀態的變化。

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

新建 Store

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

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
    }
})
複製代碼

操做狀態兩種方式

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

管理狀態 states

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

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

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

const Counter = {
	template: '<div>{{count}}<div>',
	computed: {
		count() {
			return store.state.count
		}
	}
}
複製代碼

管理取值器 getters

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

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

管理 mutations

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

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

而後在mutations 文件中管理

import * as MutationTypes from './mutationTypes'
export default {
    [MutationTypes.ADDONE]: function(state) {
        state.count = state.count + 1
    },
    //...
}
複製代碼
this.$store.commit(MutationTypes.ADDONE)
複製代碼

管理 actions

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

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

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

分模塊管理狀態

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

import count from './modules/count'
export default new Vuex.Store({
    modules: {
        count
    }
})
複製代碼

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

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)
    }
}
複製代碼

應用場景

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

組件通訊

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

當一個組件經過 commit 提交 mutation 就改了 state,其餘組件就能夠經過store.state獲取最新的state,這樣一來就至關於更新的值經過 store 傳遞給了其餘組件,不只實現了一對一的通訊,還實現了一對多的通訊。

狀態共享

咱們常用的一個場景就是權限管理

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

/** * 判斷用戶有沒有權限訪問頁面 */
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
複製代碼

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

數據緩存

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

這個數據緩存適用於:頁面加載後緩存數據,刷新頁面請求數據的場景。在通常Hybrid中,通常不存在刷新頁面這個按鈕,因此使用 vuex 緩存數據能夠應對大多數場景。

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

若是須要持久化緩存,結合瀏覽器或 APP 緩存更佳。

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' } })
    }
}
複製代碼

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

在寫後臺管理平臺時候,常常會有 list 選型組件,裏面數據從服務端拿的數據。若是咱們把這個 list 數據存儲起來,下次再次使用,直接從 store 裏面拿,這樣咱們就不用再去請求數據了。至關於減小了一次請求。

如何使用過濾器,編寫本身的過濾器

原理

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

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

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

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

應該選擇哪一種方式呢?

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

1. 可實現性

  • 使用 computed 實現成功,咱們知道computed不是一個函數是沒法傳參的,這裏有個技巧,return 一個函數接受傳過來的參數
// ...
  computed: {
    convertIdToName() {
      return function(value) {
        const item = list.find(function(item) {
          return item.id === value
        })
        return item.name
      }
    }
  }
複製代碼

  • 使用 methods 實現成功,這裏直接能夠傳參數,一種常規的寫法。

注意 methodscomputeddata 相互以前是不能同名的

// ...
  methods: {
    convertIdToName(value) {
      const item = list.find(function(item) {
        return item.id === value
      })
      return item.name
    }
  }
複製代碼
  • 使用 utilsmethods 差很少基本上也能夠實現
  • 使用 filter 也是實現的,有個能夠和methodscomputed同名哦
filters: {
    console.log(this.render)
    convertIdToName(value) {
      const item = list.find(function(item) {
        return item.id === value
      })
      return item.name
    }
  },
複製代碼

總的來講他們所有均可以實現這個需求

2. 侷限性

  • computedmethodsdata 三者互不一樣名,他們沒辦法被其餘組件使用,除非經過 mixins
  • filtersutils 沒法訪問 this ,也就是於響應式絕緣。可是經過定義全局filters,能夠其餘地方使用,另外還能夠直接加載第三方filterutils

3. 總結比較

filtersutils 歸屬一對,他們既是脫離了 this,得到了自由,又是被this 棄之門外。相反 methodscomputedthis 牢牢站在一塊兒,但又是沒法得到自由。

例子

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

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 + ',';
    }));
}
複製代碼

使用 vue-filter 插件

兩款插件

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

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

有須要的話,我通常就用第二個了,大多數都是本身寫一下小過濾器

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

註冊全局過濾器

遍歷過濾屬性值,一次性所有註冊

for (const key in filters) {
    Vue.filter(key, filters[key])
}
複製代碼

如何使用 Jest 測試你的代碼

原理

咱們思考一下測試 js 代碼須要哪些東西

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

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

Jest + Vue

安裝依賴

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

項目配置

// 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'
  //...
}
複製代碼

單元測試

測試 utils 工具類

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

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('')
    })
  })
})
複製代碼

這樣一測試,發現原來咱們以前寫的工具備這麼多漏洞

測試正常輸入所有經過了,非正常輸入失敗了,根據測試用例改進咱們的代碼

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
}
複製代碼

如今測試都經過了呢

測試 components 單文件組件

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

<template lang="pug">
  .hello
    h1 {{ msg }}
</template>
<script> export default { props: { msg: String } } </script>

複製代碼
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)
  })
})
複製代碼

測試 api 請求

異步測試有幾種常見寫法

  • asyncawait
  • done()

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

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

測試代碼

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)
  })
})
複製代碼

功能模塊測試

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

爲何我說只有 20% 的工做量呢?由於這些都是不怎麼變化的邏輯,是一勞永逸的事情。長遠來講佔用的工做量確實不多。

可是有些狀況業務仍是必須得測,也就是必需要功能模塊集成測試。

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

快照測試

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

好比我用它來測試一個配置文件的變更

這是咱們一個配置文件

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']
複製代碼

使用快照測試

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'
    })
  })
})

複製代碼

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

改動配置再次測試,未經過測試
這時若是要改變配置怎麼辦呢? 只需同時改一下用例就行了。快照將再次寫入快照生成版本2,這樣配置改動也有根據了

TDD 與 BDD

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

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

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

總結

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

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

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

項目有時間會盡可能完善

寫實踐總結性文章真的很耗費時間。如何文章中有幫到你的地方分享下唄,讓更多人看到!

相關文章
相關標籤/搜索