vue each_Vue 应用单元测试的策略与实践 05 - 测试奖杯策略
本文首發于 Vue 應用單元測試的策略與實踐 05 - 測試獎杯策略 | 呂立青的博客
歡迎關注知乎專欄 —— 前端的逆襲(凡可 JavaScript,終將 JavaScript。)
歡迎關注我的博客,知乎,GitHub,掘金。
本文的目標
單元測試的特點及其位置
前言從敏捷:團隊和企業的高響應力談到單元測試,可能有同學會問,高響應力這個事情我認可,也認可快速開發的同時,質量也很重要。但是,為了達到“保障質量”的目的,不一定得通過測試呀,就算需要測試,也不一定得通過單元測試。
這是一個好的問題。為了達到保障質量這個目標,測試當然只是其中一個方式,穩定的自動化部署、集成流水線、良好的代碼架構、甚至于團隊架構的必要調整等,都是必須跟上的基礎設施。自動化測試不是解決質量問題的銀彈,多方共同提升才可能起到效果。
即便我們談自動化測試,也未必全部都是單元測試。我們對自動化測試套件寄予的厚望是,它能幫我們安全重構已有代碼、快速回歸已有功能、保存業務上下文。測試種類多種多樣,為什么我們要重點談單元測試呢?原因很簡單,因為它寫起來相對最容易、運行速度最快、反饋效果又最直接。
測試獎杯 :軟件測試的分層策略
測試獎杯(Testing Trophy)是一種自下而上的 Web 應用測試策略。其實這是在說我們需要編寫_恰到好處的_測試,給予團隊足夠的信心 —— 正確的測試,而_不是_僅僅追求達到100%的測試覆蓋率而已。
使用測試獎杯策略,我們可以將這些自動化測試技術進行分層:
- 使用靜態類型系統和linter 來捕獲拼寫或語法之類的基本錯誤。
- 編寫有效單元測試 需要特別針對于應用的某些關鍵行為或功能。
- 編寫集成測試 以確保 Web 應用各模塊之間能夠正常協調工作。
- 創建端到端(e2e)功能測試 對關鍵路徑進行自動化點擊操作,而不是等到最終用戶來發現問題。
這種四層自動化測試提供了多快好省(放心、快速、省錢)的 JavaScript 專業化測試,最大的特點是它能夠反復執行且收益遞增,即不需要完全采納就能獲得收益,立馬見效。
性價比最高的單元測試
對于一個自動化測試策略,應該包含種類不同、關注點不同的測試,比如關注單元的單元測試、關注集成和契約的集成測試和契約測試、關注業務驗收點的端到端測試等。正常來說,我們會受到資源的限制,無法應用所有層級的測試,效果也未必最佳。
因此,我們需要有策略性地根據收益-成本的原則,考慮項目的實際情況和痛點來定制測試策略:比如三方依賴多的項目可以多寫些契約測試,業務場景多、復雜或經常回歸的場景可以多寫些端到端測試,等。但不論如何,整個測試獎杯體系中,你還是應該擁有更多低層次的單元測試,因為它們成本相對最低,運行速度最快(通常是毫秒級別),而對單元的保護價值相對更大。
Vue 應用測試的測試策略
一個常見的 Vue 應用會包括這么幾個層面:組件、數據管理、Vuex、副作用等等,對于不同的項目應該有一定的適應性。Vue + Vuex 架構中的不同元素有不同的特點,因此即便是單元測試,我們也會有針對性的測試策略:
Component 的測試標準
組件測試其實是前端測試中實踐最多,但各方看法最不統一的地方,這也是前后端在談論單元測試時最大的分歧所在。Vue 組件是一個高度自治的單元,從分類上來看,它大概有這么幾類:
- 展示型業務組件
- 容器型業務組件
- 通用 UI 組件
- 功能型組件
對于 Vue 組件測什么不測什么有一些判斷標準:除去功能型組件,其他類型的組件一般是以渲染出一個語法樹 render() 為終點的,它描述了頁面的 UI 內容、結構、樣式和一些邏輯 component(props) => UI。內容、結構和樣式,比起測試,直接在頁面上調試反饋效果更好。測也不是不行,但都難免有不穩定的成本在;邏輯這塊,有一測的價值,但需要控制好依賴。綜合上面提到的測試原則進行考慮,我的建議是:兩測兩不測。
- 組件分支渲染邏輯必須測
- 事件調用和參數傳遞一般要測
- 連接 vuex 的高階 SMART 組件不測
- 渲染出來的 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%的覆蓋率嗎?
- 多少次測試就足夠了?
未完待續……
## 單元測試基礎
- [x] ### 單元測試與自動化的意義
- [x] ### 為什么選擇 Jest
- [x] ### Jest 的基本用法
- [x] ### 該如何測試異步代碼?
## Vue 單元測試
- [x] ### Vue 組件的渲染方式
- [x] ### Wrapper find() 方法與選擇器
- [x] ### UI 組件交互行為的測試
## Vuex 單元測試
- [x] ### CQRS 與 Redux-like 架構
- [x] ### 如何對 Vuex 進行單元測試
- [x] ### Vue組件和Vuex store的交互
## Vue 應用測試策略
- [x] ### 單元測試的特點及其位置
- [x] ### 測試獎杯 :軟件測試的分層策略
- [x] ### 單元測試的F.I.R.S.T原則
## Vue 單元測試的落地
- [ ] ### 應用測試策略落地的幾點建議
您可能也會喜歡:
- Vue 應用單元測試的策略與實踐 04 - Vuex 單元測試
- Vue 應用單元測試的策略與實踐 06 - 如何落地的幾點建議
- Vue 應用單元測試的策略與實踐 03 - Vue 組件單元測試
- Vue 應用單元測試的策略與實踐 02 - 單元測試基礎
- Vue 應用單元測試的策略與實踐 01 - 前言和目標
總結
以上是生活随笔為你收集整理的vue each_Vue 应用单元测试的策略与实践 05 - 测试奖杯策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网吧几个月没开门网吧几个月没开门了
- 下一篇: vue方法调用失败后多次调用_浅析Vue