Vue 應用單元測試的策略與實踐 05 - 測試獎盃策略

本文首發於 Vue 應用單元測試的策略與實踐 05 - 測試獎盃策略 | 呂立青的博客javascript

歡迎關注知乎專欄 —— 前端的逆襲(凡可 JavaScript,終將 JavaScript。)html

歡迎關注個人博客知乎GitHub掘金前端


本文的目標

  1. Vue 項目中測試收益如何最大化,如何配置高性價比的測試策略,即什麼地方最該花力氣測試,什麼地方又能夠暫且放一放?
// Given
一個具有UT基礎但找不到着力點的求索之徒🐒
// When
當他🚶閱讀本文的Vue應用測試策略部分
// Then
他可以找到測試的重點,從新燃起對UT的熱情🔥
他可以在項目背景下合理配置單元測試的測試策略
複製代碼

單元測試的特色及其位置

前言從敏捷:團隊和企業的高響應力談到單元測試,可能有同窗會問,高響應力這個事情我承認,也承認快速開發的同時,質量也很重要。可是,爲了達到「保障質量」的目的,不必定得經過測試呀,就算須要測試,也不必定得經過單元測試。vue

這是一個好的問題。爲了達到保障質量這個目標,測試固然只是其中一個方式,穩定的自動化部署、集成流水線、良好的代碼架構、甚至於團隊架構的必要調整等,都是必須跟上的基礎設施。自動化測試不是解決質量問題的銀彈,多方共同提高才可能起到效果。java

即使咱們談自動化測試,也未必所有都是單元測試。咱們對自動化測試套件寄予的厚望是,它能幫咱們安全重構已有代碼快速回歸已有功能保存業務上下文。測試種類多種多樣,爲何咱們要重點談單元測試呢?緣由很簡單,由於它寫起來相對最容易、運行速度最快、反饋效果又最直接。git

測試獎盃🏆:軟件測試的分層策略

測試獎盃(Testing Trophy)是一種自下而上的 Web 應用測試策略。其實這是在說咱們須要編寫_恰到好處的_測試,給予團隊足夠的信心 —— 正確的測試,而_不是_僅僅追求達到100%的測試覆蓋率而已。程序員

測試獎盃的四個部分

使用測試獎盃策略,咱們能夠將這些自動化測試技術進行分層:github

  • 使用靜態類型系統和linter 來捕獲拼寫或語法之類的基本錯誤。
  • 編寫有效單元測試 須要特別針對於應用的某些關鍵行爲或功能。
  • 編寫集成測試 以確保 Web 應用各模塊之間可以正常協調工做。
  • 建立端到端(e2e)功能測試 對關鍵路徑進行自動化點擊操做,而不是等到最終用戶來發現問題。

這種四層自動化測試提供了多快好省(放心、快速、省錢)的 JavaScript 專業化測試,最大的特色是它可以反覆執行且收益遞增,即不須要徹底採納就能得到收益,立馬見效。vuex

性價比最高的單元測試

所以,咱們須要有策略性地根據收益-成本的原則,考慮項目的實際狀況和痛點來定製測試策略:好比三方依賴多的項目能夠多寫些契約測試,業務場景多、複雜或常常回歸的場景能夠多寫些端到端測試,等。但不論如何,整個測試獎盃體系中,你仍是應該擁有更多低層次的單元測試,由於它們成本相對最低,運行速度最快(一般是毫秒級別),而對單元的保護價值相對更大。數據庫

Vue 應用測試的測試策略

一個常見的 Vue 應用會包括這麼幾個層面:組件、數據管理、Vuex、反作用等等,對於不一樣的項目應該有必定的適應性。Vue + Vuex 架構中的不一樣元素有不一樣的特色,所以即使是單元測試,咱們也會有針對性的測試策略:

架構層級 測試內容 測試策略 解釋
action 層 1. 是否獲取了正確的參數
2. 是否正確地調用了 API
3. 是否使用了正確的返回值存取回 Vuex 中
4. 業務分支邏輯
5. 異常邏輯
這五個業務點建議 100% 覆蓋 這個層級主要包含前述 5 大方面的業務邏輯,進行測試頗有重構價值
mutation 層 是否正確完成計算 有邏輯的 mutation 要求 100%覆蓋率 這個層級輸入輸出明確,又包含業務計算,很是適合單元測試
getter 層 是否正確完成計算 有邏輯的 getter 要求 100%覆蓋率 這個層級輸入輸出明確,又包含業務計算,很是適合單元測試
component 層 是否渲染了正確的組件 1. 組件的分支渲染邏輯要求100%覆蓋
2. 交互事件的調用參數通常要求100%覆蓋
3. 被 connect 過的組件不測
這個層級最爲複雜,仍是以「代價最低,收益最高」爲指導原則進行
UI 層 組件是否渲染了正確的樣式 1. 純 UI 不測
2. CSS 不測
這個層級以我目前理解來講測試較難穩定,成本又較高
utils 層 各類輔助工具函數 沒有反作用的必須 100% 覆蓋

Component 的測試標準

組件測試實際上是前端測試中實踐最多,但各方見解最不統一的地方,這也是先後端在談論單元測試時最大的分歧所在。Vue 組件是一個高度自治的單元,從分類上來看,它大概有這麼幾類:

  • 展現型業務組件
  • 容器型業務組件
  • 通用 UI 組件
  • 功能型組件

對於 Vue 組件測什麼不測什麼有一些判斷標準:除去功能型組件,其餘類型的組件通常是以渲染出一個語法樹 render() 爲終點的,它描述了頁面的 UI 內容、結構、樣式和一些邏輯 component(props) => UI。內容、結構和樣式,比起測試,直接在頁面上調試反饋效果更好。測也不是不行,但都不免有不穩定的成本在;邏輯這塊,有一測的價值,但須要控制好依賴。綜合上面提到的測試原則進行考慮,個人建議是:兩測兩不測。

  • 組件分支渲染邏輯必須測
  • 事件調用和參數傳遞通常要測
  • 鏈接 vuex 的高階 SMART 組件不測
  • 渲染出來的 UI 不在單元測試層級測

總結一下,其實每種組件都要測渲染分支事件調用,跟組件類型根本沒必然的關聯…

組件類型 / 測試內容 分支渲染邏輯 事件調用 @connect 純 UI
展現型組件
容器型組件
通用 UI 組件
功能型組件

單元測試的 F.I.R.S.T 原則

編寫容易維護的單元測試有一些原則,這些原則對於任何語言、任何層級的測試都適用。這些原則不是新東西,但老是須要時時溫故知新,前人總結成 F.I.R.S.T 五個原則,以此爲鏡,能夠時時檢驗你的單元測試是否高效:

  • F Fast:測試須要頻繁運行,所以要能快速運行;
  • I Independent:測試應該相互獨立,一次只測一條分支;
  • R Repeatable:測試自己不包含邏輯,能在任何環境中重複;
  • S Self-validating:只關注輸入輸出,不關注內部實現;
  • T Timely:測試應該及時編寫,表達力極強,易於閱讀;

Fast:運行速度快,頻繁運行

單元測試只有在毫秒級別內完成,開發者纔會願意頻繁地運行它,將其做爲快速反饋的手段也才能成立。那麼爲了使單元測試更快,咱們須要:

  • 儘量地避免依賴。除了恰當設計好對象,關於避免依賴我已知有兩種不一樣的見解:
    • 使用mock適當隔離掉三方的依賴(如數據庫、網絡、文件等)
    • 避免mock,換用更快速的數據庫、啓動輕量級服務器、重點測試文件內容等來迂迴
  • 將依賴、集成等耗時、依賴三方返回的地方放到更高層級的測試中,有策略性地去作

Independent:一次只測一條分支

一般來講,一條分支就是一個業務場景,是作任務分解(Tasking)過程的一個細粒度的task。爲何測試只測一條分支呢?很顯然,如此你才能給它一個好的描述,這個測試才能保護這個特定的業務場景,掛了的時候能給你細緻到輸入輸出級別的業務反饋。

常見的反模式是,實現自己就作了太多的事情,不符合單一功能(SRP)原則。若是你發現某個模塊的單元測試特別難寫的話,那麼這個模塊的實現自己或輸入/輸出就足夠繁瑣,應看成爲一種某味道識別出來進行重構。

Repeatable:測試不包含邏輯

跟寫聲明式的代碼同樣的道理,測試須要都是簡單的聲明:準備數據、調用函數、斷言,讓人一眼就明白這個測試在測什麼。若是含有邏輯,你讀的時候就要多花時間理解;一旦測試掛掉,你咋知道是實現掛了仍是測試自己就掛了呢?特別是對於一些時間或者隨機數相關的測試,必定不可以從測試中隨機生成這樣的測試數據,保證測試中不包含任何過多的邏輯。

但對於一些項目中的 utils 來講,咱們指望 util 都是純函數,便是不依賴外部狀態、不改變參數值、不維護內部狀態的函數。因爲可能是數據驅動,一個輸入對應一個輸出,而且不須要準備任何依賴,這使得它多了一種測試的選擇,也便是參數化測試的方式。

參數化測試能夠提高數據準備效率,同時依然能保持詳細的用例信息、錯誤提示等優勢。jest 從 23 後就內置了對參數化測試的支持,以下:

test.each([
  [['0', '99'], 0.99, '(整數部分爲0時也應返回)'],
  [['5', '00'], 5, '(小數部分不足時應該補0)'],
  [['5', '10'], 5.1, '(小數部分不足時應該補0)'],
  [['4', '38'], 4.38, '(小數部分不足時應該補0)'],
  [['4', '99'], 4.994, '(超過默認2位的小數的直接截斷,不四捨五入)'],
  [['4', '99'], 4.995, '(超過默認2位的小數的直接截斷,不四捨五入)'],
  [['4', '99'], 4.996, '(超過默認2位的小數的直接截斷,不四捨五入)'],
  [['-0', '50'], -0.5, '(整數部分爲負數時應該保留負號)'],
])(
  'should return %s when number is %s (%s)',
  (expected, input, description) => {
    expect(truncateAndPadTrailingZeros(input)).toEqual(expected)
  }
)
複製代碼

固然,對純數據驅動的測試,也有一些不一樣的見解,認爲這樣可能丟失一些描述業務場景的測試描述。因此這種方式還主要看項目組的接受度。

Self-validating:只關注輸入輸出,不關注內部實現

好比購物車「計算總價格」這樣的一個功能,測試自己不關注內部實現:你能夠用reduce實現,也能夠本身寫for循環實現。只要測試輸入沒有變,輸出就不該該變。這個特性,是測試支撐重構的基礎。由於重構指的是,在不改變軟件外部可觀測行爲的基礎上,調整軟件內部的實現。

另外,還有一些測試實現代碼的執行次序。這也是一種「關注內部實現」的測試,這就使得除了輸入輸出外,還有「執行次序」這個因素可能使測試掛掉。顯然,這樣的測試也不利於重構的開展。

此外,對外部依賴採起mock策略,一樣是某種程度上的「關注內部實現」,由於mock的失敗一樣將致使測試的失敗,而非真正業務場景的失敗。對待mock的態度,肖鵬有篇文章Mock的七宗罪對此展開了詳細描述,應當謹慎使用。

Timely:表達力極強,易於閱讀

測試應該及時編寫,只有在當下最熟悉業務的時候,纔可以寫出表達力最強的測試。而當咱們在將來不當心破壞某個功能時,表達力強的測試才能在失敗的時候給你很是迅速的反饋。它講的是兩方面:

  • 看到測試時,你就知道它測的業務點是啥
  • 測試掛掉時,能清楚地知道失敗的業務場景、指望數據與實際輸出的差別

總結起來,這些表達力主要體如今如下的方面:

  • 測試描述。遵循上一條原則(一個單元測試只測一個分支)的狀況下,描述一般能寫出一個至關詳細的業務場景。這爲測試的讀者提供了極佳的業務上下文
  • 測試數據準備。無關的測試數據(好比對象中的不少無關字段)不該該寫出來,應只准備能體現測試業務的最小數據
  • 輸出報告。選用斷言工具時,應注意除了要提供測試結果,還要能準確提供「指望值」與「實際值」的差別

上述第三點有些測試框架提供了反例,好比說chai和sinon提供的斷言API就不如jest友好,體如今:

  • expect(array).to.eql(array)出錯的時候,只能報告說expect [Array (42)] to equal [Array (42)],具體是哪一個數據不匹配,根本沒報告
  • expect(sinonStub.calledWith(args)).to.be.true出錯的時候,會報告說expect false to be true。廢話,我還不知道掛了麼,可是那個stub究竟被什麼參數調用則沒有報告

總結一下

「測試須要花費太多時間和精力。」

  • 沒時間。 我知道,你已經很忙了。
  • 沒有明顯的投資回報率。 我知道,你不肯定測試到底能帶來什麼。
  • 沒有_辦法_測試一切。 我知道,大多數測試都是所謂的_點點點……_。這感受就像浪費時間,咱們都喜歡開發新功能,而不僅是對着舊功能「點點點……」。

事實上,沒有人有時間。可是,不管如何:

你所開發的軟件終將被測試。若是不是由你本身發現,那麼就是由你的用戶發現(💥Bug)。

「懶惰」是程序員最大的美德

Perl語言的發明人Larry Wall說,好的程序員有3種美德: 懶惰、急躁和傲慢(Laziness, Impatience and hubris)。

懶惰:是這樣一種品質,它使得你花大力氣去避免消耗過多的精力。它敦促你寫出節省體力的程序,同時別人也能利用它們。爲此你會寫出完善的測試或文檔,以避免別人問你太多問題。

想象一下,將測試軟件的繁重工做所有外包給機器。

你是開發工程師呀,這個時代最偉大的腦力工做者啊!你知道人類在處理重複性任務的時候都很糟糕,可是你還知道**_機器_很是很是擅長複雜的重複性任務。**更專業的開發人員就是會使用計算機來作自動化測試 —— 一成天都在綿綿不休地進行,幫你處理這些測試軟件的繁重工做。

  • 自動化測試是專業的。
  • 自動化測試是你的後盾,是你的肌肉。
  • 自動化測試是你的祕密武器……

時不時,問一下本身這幾個問題:

  • ,還能夠如何偷懶?
  • 應該讓計算機幫忙測點什麼?
  • 計算機該在何時進行測試?
  • 須要100%的覆蓋率嗎?
  • 多少次測試就足夠了?

未完待續……

## 單元測試基礎

  • ### 單元測試與自動化的意義
  • ### 爲何選擇 Jest
  • ### Jest 的基本用法
  • ### 該如何測試異步代碼?

## Vue 單元測試

  • ### Vue 組件的渲染方式
  • ### Wrapper find() 方法與選擇器
  • ### UI 組件交互行爲的測試

## Vuex 單元測試

  • ### CQRS 與 Redux-like 架構
  • ### 如何對 Vuex 進行單元測試
  • ### Vue組件和Vuex store的交互

## Vue 應用測試策略

  • ### 單元測試的特色及其位置
  • ### 測試獎盃🏆:軟件測試的分層策略
  • ### 單元測試的F.I.R.S.T原則

## Vue 單元測試的落地

  • ### 應用測試策略落地的幾點建議

您可能也會喜歡:

相關文章
相關標籤/搜索