本文首發於 Vue 應用單元測試的策略與實踐 05 - 測試獎盃策略 | 呂立青的博客javascript
歡迎關注知乎專欄 —— 前端的逆襲(凡可 JavaScript,終將 JavaScript。)html
// Given
一個具有UT基礎但找不到着力點的求索之徒🐒
// When
當他🚶閱讀本文的Vue應用測試策略部分
// Then
他可以找到測試的重點,從新燃起對UT的熱情🔥
他可以在項目背景下合理配置單元測試的測試策略
複製代碼
前言從敏捷:團隊和企業的高響應力談到單元測試,可能有同窗會問,高響應力這個事情我承認,也承認快速開發的同時,質量也很重要。可是,爲了達到「保障質量」的目的,不必定得經過測試呀,就算須要測試,也不必定得經過單元測試。vue
這是一個好的問題。爲了達到保障質量這個目標,測試固然只是其中一個方式,穩定的自動化部署、集成流水線、良好的代碼架構、甚至於團隊架構的必要調整等,都是必須跟上的基礎設施。自動化測試不是解決質量問題的銀彈,多方共同提高才可能起到效果。java
即使咱們談自動化測試,也未必所有都是單元測試。咱們對自動化測試套件寄予的厚望是,它能幫咱們安全重構已有代碼、快速回歸已有功能、保存業務上下文。測試種類多種多樣,爲何咱們要重點談單元測試呢?緣由很簡單,由於它寫起來相對最容易、運行速度最快、反饋效果又最直接。git
測試獎盃(Testing Trophy)是一種自下而上的 Web 應用測試策略。其實這是在說咱們須要編寫_恰到好處的_測試,給予團隊足夠的信心 —— 正確的測試,而_不是_僅僅追求達到100%的測試覆蓋率而已。程序員
使用測試獎盃策略,咱們能夠將這些自動化測試技術進行分層:github
這種四層自動化測試提供了多快好省(放心、快速、省錢)的 JavaScript 專業化測試,最大的特色是它可以反覆執行且收益遞增,即不須要徹底採納就能得到收益,立馬見效。vuex
所以,咱們須要有策略性地根據收益-成本的原則,考慮項目的實際狀況和痛點來定製測試策略:好比三方依賴多的項目能夠多寫些契約測試,業務場景多、複雜或常常回歸的場景能夠多寫些端到端測試,等。但不論如何,整個測試獎盃體系中,你仍是應該擁有更多低層次的單元測試,由於它們成本相對最低,運行速度最快(一般是毫秒級別),而對單元的保護價值相對更大。數據庫
一個常見的 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% 覆蓋 |
組件測試實際上是前端測試中實踐最多,但各方見解最不統一的地方,這也是先後端在談論單元測試時最大的分歧所在。Vue 組件是一個高度自治的單元,從分類上來看,它大概有這麼幾類:
對於 Vue 組件測什麼不測什麼有一些判斷標準:除去功能型組件,其餘類型的組件通常是以渲染出一個語法樹 render()
爲終點的,它描述了頁面的 UI 內容、結構、樣式和一些邏輯 component(props) => UI
。內容、結構和樣式,比起測試,直接在頁面上調試反饋效果更好。測也不是不行,但都不免有不穩定的成本在;邏輯這塊,有一測的價值,但須要控制好依賴。綜合上面提到的測試原則進行考慮,個人建議是:兩測兩不測。
總結一下,其實每種組件都要測渲染分支和事件調用,跟組件類型根本沒必然的關聯…
組件類型 / 測試內容 | 分支渲染邏輯 | 事件調用 | @connect |
純 UI |
---|---|---|---|---|
展現型組件 | ✅ | ✅ | – | ❌ |
容器型組件 | ✅ | ✅ | ❌ | ❌ |
通用 UI 組件 | ✅ | ✅ | – | ❌ |
功能型組件 | ✅ | ✅ | ❌ | ❌ |
編寫容易維護的單元測試有一些原則,這些原則對於任何語言、任何層級的測試都適用。這些原則不是新東西,但老是須要時時溫故知新,前人總結成 F.I.R.S.T 五個原則,以此爲鏡,能夠時時檢驗你的單元測試是否高效:
單元測試只有在毫秒級別內完成,開發者纔會願意頻繁地運行它,將其做爲快速反饋的手段也才能成立。那麼爲了使單元測試更快,咱們須要:
一般來講,一條分支就是一個業務場景,是作任務分解(Tasking)過程的一個細粒度的task。爲何測試只測一條分支呢?很顯然,如此你才能給它一個好的描述,這個測試才能保護這個特定的業務場景,掛了的時候能給你細緻到輸入輸出級別的業務反饋。
常見的反模式是,實現自己就作了太多的事情,不符合單一功能(SRP)原則。若是你發現某個模塊的單元測試特別難寫的話,那麼這個模塊的實現自己或輸入/輸出就足夠繁瑣,應看成爲一種某味道識別出來進行重構。
跟寫聲明式的代碼同樣的道理,測試須要都是簡單的聲明:準備數據、調用函數、斷言,讓人一眼就明白這個測試在測什麼。若是含有邏輯,你讀的時候就要多花時間理解;一旦測試掛掉,你咋知道是實現掛了仍是測試自己就掛了呢?特別是對於一些時間或者隨機數相關的測試,必定不可以從測試中隨機生成這樣的測試數據,保證測試中不包含任何過多的邏輯。
但對於一些項目中的 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)
}
)
複製代碼
固然,對純數據驅動的測試,也有一些不一樣的見解,認爲這樣可能丟失一些描述業務場景的測試描述。因此這種方式還主要看項目組的接受度。
好比購物車「計算總價格」這樣的一個功能,測試自己不關注內部實現:你能夠用reduce
實現,也能夠本身寫for
循環實現。只要測試輸入沒有變,輸出就不該該變。這個特性,是測試支撐重構的基礎。由於重構指的是,在不改變軟件外部可觀測行爲的基礎上,調整軟件內部的實現。
另外,還有一些測試實現代碼的執行次序。這也是一種「關注內部實現」的測試,這就使得除了輸入輸出外,還有「執行次序」這個因素可能使測試掛掉。顯然,這樣的測試也不利於重構的開展。
此外,對外部依賴採起mock策略,一樣是某種程度上的「關注內部實現」,由於mock的失敗一樣將致使測試的失敗,而非真正業務場景的失敗。對待mock的態度,肖鵬有篇文章Mock的七宗罪對此展開了詳細描述,應當謹慎使用。
測試應該及時編寫,只有在當下最熟悉業務的時候,纔可以寫出表達力最強的測試。而當咱們在將來不當心破壞某個功能時,表達力強的測試才能在失敗的時候給你很是迅速的反饋。它講的是兩方面:
總結起來,這些表達力主要體如今如下的方面:
上述第三點有些測試框架提供了反例,好比說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)。
懶惰:是這樣一種品質,它使得你花大力氣去避免消耗過多的精力。它敦促你寫出節省體力的程序,同時別人也能利用它們。爲此你會寫出完善的測試或文檔,以避免別人問你太多問題。
想象一下,將測試軟件的繁重工做所有外包給機器。
你是開發工程師呀,這個時代最偉大的腦力工做者啊!你知道人類在處理重複性任務的時候都很糟糕,可是你還知道**_機器_很是很是擅長複雜的重複性任務。**更專業的開發人員就是會使用計算機來作自動化測試 —— 一成天都在綿綿不休地進行,幫你處理這些測試軟件的繁重工做。
時不時,問一下本身這幾個問題:
## 單元測試基礎
## Vue 單元測試
find()
方法與選擇器## Vuex 單元測試
Redux-like
架構## Vue 應用測試策略
## Vue 單元測試的落地