vue each_Vue 应用单元测试的策略与实践 05 - 测试奖杯策略
本文首發(fā)于 Vue 應用單元測試的策略與實踐 05 - 測試獎杯策略 | 呂立青的博客
歡迎關(guān)注知乎專欄 —— 前端的逆襲(凡可 JavaScript,終將 JavaScript。)
歡迎關(guān)注我的博客,知乎,GitHub,掘金。
本文的目標
單元測試的特點及其位置
前言從敏捷:團隊和企業(yè)的高響應力談到單元測試,可能有同學會問,高響應力這個事情我認可,也認可快速開發(fā)的同時,質(zhì)量也很重要。但是,為了達到“保障質(zhì)量”的目的,不一定得通過測試呀,就算需要測試,也不一定得通過單元測試。
這是一個好的問題。為了達到保障質(zhì)量這個目標,測試當然只是其中一個方式,穩(wěn)定的自動化部署、集成流水線、良好的代碼架構(gòu)、甚至于團隊架構(gòu)的必要調(diào)整等,都是必須跟上的基礎(chǔ)設(shè)施。自動化測試不是解決質(zhì)量問題的銀彈,多方共同提升才可能起到效果。
即便我們談自動化測試,也未必全部都是單元測試。我們對自動化測試套件寄予的厚望是,它能幫我們安全重構(gòu)已有代碼、快速回歸已有功能、保存業(yè)務上下文。測試種類多種多樣,為什么我們要重點談單元測試呢?原因很簡單,因為它寫起來相對最容易、運行速度最快、反饋效果又最直接。
測試獎杯 :軟件測試的分層策略
測試獎杯(Testing Trophy)是一種自下而上的 Web 應用測試策略。其實這是在說我們需要編寫_恰到好處的_測試,給予團隊足夠的信心 —— 正確的測試,而_不是_僅僅追求達到100%的測試覆蓋率而已。
使用測試獎杯策略,我們可以將這些自動化測試技術(shù)進行分層:
- 使用靜態(tài)類型系統(tǒng)和linter 來捕獲拼寫或語法之類的基本錯誤。
- 編寫有效單元測試 需要特別針對于應用的某些關(guān)鍵行為或功能。
- 編寫集成測試 以確保 Web 應用各模塊之間能夠正常協(xié)調(diào)工作。
- 創(chuàng)建端到端(e2e)功能測試 對關(guān)鍵路徑進行自動化點擊操作,而不是等到最終用戶來發(fā)現(xiàn)問題。
這種四層自動化測試提供了多快好省(放心、快速、省錢)的 JavaScript 專業(yè)化測試,最大的特點是它能夠反復執(zhí)行且收益遞增,即不需要完全采納就能獲得收益,立馬見效。
性價比最高的單元測試
對于一個自動化測試策略,應該包含種類不同、關(guān)注點不同的測試,比如關(guān)注單元的單元測試、關(guān)注集成和契約的集成測試和契約測試、關(guān)注業(yè)務驗收點的端到端測試等。正常來說,我們會受到資源的限制,無法應用所有層級的測試,效果也未必最佳。
因此,我們需要有策略性地根據(jù)收益-成本的原則,考慮項目的實際情況和痛點來定制測試策略:比如三方依賴多的項目可以多寫些契約測試,業(yè)務場景多、復雜或經(jīng)?;貧w的場景可以多寫些端到端測試,等。但不論如何,整個測試獎杯體系中,你還是應該擁有更多低層次的單元測試,因為它們成本相對最低,運行速度最快(通常是毫秒級別),而對單元的保護價值相對更大。
Vue 應用測試的測試策略
一個常見的 Vue 應用會包括這么幾個層面:組件、數(shù)據(jù)管理、Vuex、副作用等等,對于不同的項目應該有一定的適應性。Vue + Vuex 架構(gòu)中的不同元素有不同的特點,因此即便是單元測試,我們也會有針對性的測試策略:
Component 的測試標準
組件測試其實是前端測試中實踐最多,但各方看法最不統(tǒng)一的地方,這也是前后端在談論單元測試時最大的分歧所在。Vue 組件是一個高度自治的單元,從分類上來看,它大概有這么幾類:
- 展示型業(yè)務組件
- 容器型業(yè)務組件
- 通用 UI 組件
- 功能型組件
對于 Vue 組件測什么不測什么有一些判斷標準:除去功能型組件,其他類型的組件一般是以渲染出一個語法樹 render() 為終點的,它描述了頁面的 UI 內(nèi)容、結(jié)構(gòu)、樣式和一些邏輯 component(props) => UI。內(nèi)容、結(jié)構(gòu)和樣式,比起測試,直接在頁面上調(diào)試反饋效果更好。測也不是不行,但都難免有不穩(wěn)定的成本在;邏輯這塊,有一測的價值,但需要控制好依賴。綜合上面提到的測試原則進行考慮,我的建議是:兩測兩不測。
- 組件分支渲染邏輯必須測
- 事件調(diào)用和參數(shù)傳遞一般要測
- 連接 vuex 的高階 SMART 組件不測
- 渲染出來的 UI 不在單元測試層級測
總結(jié)一下,其實每種組件都要測渲染分支和事件調(diào)用,跟組件類型根本沒必然的關(guān)聯(lián)…
單元測試的 F.I.R.S.T 原則
編寫容易維護的單元測試有一些原則,這些原則對于任何語言、任何層級的測試都適用。這些原則不是新東西,但總是需要時時溫故知新,前人總結(jié)成 F.I.R.S.T 五個原則,以此為鏡,可以時時檢驗你的單元測試是否高效:
- F Fast:測試需要頻繁運行,因此要能快速運行;
- I Independent:測試應該相互獨立,一次只測一條分支;
- R Repeatable:測試本身不包含邏輯,能在任何環(huán)境中重復;
- S Self-validating:只關(guān)注輸入輸出,不關(guān)注內(nèi)部實現(xiàn);
- T Timely:測試應該及時編寫,表達力極強,易于閱讀;
Fast:運行速度快,頻繁運行
單元測試只有在毫秒級別內(nèi)完成,開發(fā)者才會愿意頻繁地運行它,將其作為快速反饋的手段也才能成立。那么為了使單元測試更快,我們需要:
- 盡可能地避免依賴。除了恰當設(shè)計好對象,關(guān)于避免依賴我已知有兩種不同的看法:
- 使用mock適當隔離掉三方的依賴(如數(shù)據(jù)庫、網(wǎng)絡、文件等)
- 避免mock,換用更快速的數(shù)據(jù)庫、啟動輕量級服務器、重點測試文件內(nèi)容等來迂回
- 將依賴、集成等耗時、依賴三方返回的地方放到更高層級的測試中,有策略性地去做
Independent:一次只測一條分支
通常來說,一條分支就是一個業(yè)務場景,是做任務分解(Tasking)過程的一個細粒度的task。為什么測試只測一條分支呢?很顯然,如此你才能給它一個好的描述,這個測試才能保護這個特定的業(yè)務場景,掛了的時候能給你細致到輸入輸出級別的業(yè)務反饋。
常見的反模式是,實現(xiàn)本身就做了太多的事情,不符合單一功能(SRP)原則。如果你發(fā)現(xiàn)某個模塊的單元測試特別難寫的話,那么這個模塊的實現(xiàn)本身或輸入/輸出就足夠繁瑣,應當作為一種某味道識別出來進行重構(gòu)。
Repeatable:測試不包含邏輯
跟寫聲明式的代碼一樣的道理,測試需要都是簡單的聲明:準備數(shù)據(jù)、調(diào)用函數(shù)、斷言,讓人一眼就明白這個測試在測什么。如果含有邏輯,你讀的時候就要多花時間理解;一旦測試掛掉,你咋知道是實現(xiàn)掛了還是測試本身就掛了呢?特別是對于一些時間或者隨機數(shù)相關(guān)的測試,一定不能夠從測試中隨機生成這樣的測試數(shù)據(jù),保證測試中不包含任何過多的邏輯。
但對于一些項目中的 utils 來說,我們期望 util 都是純函數(shù),即是不依賴外部狀態(tài)、不改變參數(shù)值、不維護內(nèi)部狀態(tài)的函數(shù)。由于多是數(shù)據(jù)驅(qū)動,一個輸入對應一個輸出,并且不需要準備任何依賴,這使得它多了一種測試的選擇,也即是參數(shù)化測試的方式。
參數(shù)化測試可以提升數(shù)據(jù)準備效率,同時依然能保持詳細的用例信息、錯誤提示等優(yōu)點。jest 從 23 后就內(nèi)置了對參數(shù)化測試的支持,如下:
test.each([[['0', '99'], 0.99, '(整數(shù)部分為0時也應返回)'],[['5', '00'], 5, '(小數(shù)部分不足時應該補0)'],[['5', '10'], 5.1, '(小數(shù)部分不足時應該補0)'],[['4', '38'], 4.38, '(小數(shù)部分不足時應該補0)'],[['4', '99'], 4.994, '(超過默認2位的小數(shù)的直接截斷,不四舍五入)'],[['4', '99'], 4.995, '(超過默認2位的小數(shù)的直接截斷,不四舍五入)'],[['4', '99'], 4.996, '(超過默認2位的小數(shù)的直接截斷,不四舍五入)'],[['-0', '50'], -0.5, '(整數(shù)部分為負數(shù)時應該保留負號)'], ])('should return %s when number is %s (%s)',(expected, input, description) => {expect(truncateAndPadTrailingZeros(input)).toEqual(expected)} )當然,對純數(shù)據(jù)驅(qū)動的測試,也有一些不同的看法,認為這樣可能丟失一些描述業(yè)務場景的測試描述。所以這種方式還主要看項目組的接受度。
Self-validating:只關(guān)注輸入輸出,不關(guān)注內(nèi)部實現(xiàn)
比如購物車“計算總價格”這樣的一個功能,測試本身不關(guān)注內(nèi)部實現(xiàn):你可以用reduce實現(xiàn),也可以自己寫for循環(huán)實現(xiàn)。只要測試輸入沒有變,輸出就不應該變。這個特性,是測試支撐重構(gòu)的基礎(chǔ)。因為重構(gòu)指的是,在不改變軟件外部可觀測行為的基礎(chǔ)上,調(diào)整軟件內(nèi)部的實現(xiàn)。
另外,還有一些測試實現(xiàn)代碼的執(zhí)行次序。這也是一種“關(guān)注內(nèi)部實現(xiàn)”的測試,這就使得除了輸入輸出外,還有“執(zhí)行次序”這個因素可能使測試掛掉。顯然,這樣的測試也不利于重構(gòu)的開展。
此外,對外部依賴采取mock策略,同樣是某種程度上的“關(guān)注內(nèi)部實現(xiàn)”,因為mock的失敗同樣將導致測試的失敗,而非真正業(yè)務場景的失敗。對待mock的態(tài)度,肖鵬有篇文章Mock的七宗罪對此展開了詳細描述,應當謹慎使用。
Timely:表達力極強,易于閱讀
測試應該及時編寫,只有在當下最熟悉業(yè)務的時候,才能夠?qū)懗霰磉_力最強的測試。而當我們在未來不小心破壞某個功能時,表達力強的測試才能在失敗的時候給你非常迅速的反饋。它講的是兩方面:
- 看到測試時,你就知道它測的業(yè)務點是啥
- 測試掛掉時,能清楚地知道失敗的業(yè)務場景、期望數(shù)據(jù)與實際輸出的差異
總結(jié)起來,這些表達力主要體現(xiàn)在以下的方面:
- 測試描述。遵循上一條原則(一個單元測試只測一個分支)的情況下,描述通常能寫出一個相當詳細的業(yè)務場景。這為測試的讀者提供了極佳的業(yè)務上下文
- 測試數(shù)據(jù)準備。無關(guān)的測試數(shù)據(jù)(比如對象中的很多無關(guān)字段)不應該寫出來,應只準備能體現(xiàn)測試業(yè)務的最小數(shù)據(jù)
- 輸出報告。選用斷言工具時,應注意除了要提供測試結(jié)果,還要能準確提供“期望值”與“實際值”的差異
上述第三點有些測試框架提供了反例,比如說chai和sinon提供的斷言API就不如jest友好,體現(xiàn)在:
- expect(array).to.eql(array)出錯的時候,只能報告說expect [Array (42)] to equal [Array (42)],具體是哪個數(shù)據(jù)不匹配,根本沒報告
- expect(sinonStub.calledWith(args)).to.be.true出錯的時候,會報告說expect false to be true。廢話,我還不知道掛了么,但是那個stub究竟被什么參數(shù)調(diào)用則沒有報告
總結(jié)一下
“測試需要花費太多時間和精力?!?/h3>- 沒時間。 我知道,你已經(jīng)很忙了。
- 沒有明顯的投資回報率。 我知道,你不確定測試到底能帶來什么。
- 沒有_辦法_測試一切。 我知道,大多數(shù)測試都是所謂的_點點點……_。這感覺就像浪費時間,我們都喜歡開發(fā)新功能,而不只是對著舊功能“點點點……”。
事實上,沒有人有時間。但是,無論如何:
你所開發(fā)的軟件終將被測試。如果不是由你自己發(fā)現(xiàn),那么就是由你的用戶發(fā)現(xiàn)( Bug)。「懶惰」是程序員最大的美德
Perl語言的發(fā)明人Larry Wall說,好的程序員有3種美德: 懶惰、急躁和傲慢(Laziness, Impatience and hubris)。
懶惰:是這樣一種品質(zhì),它使得你花大力氣去避免消耗過多的精力。它敦促你寫出節(jié)省體力的程序,同時別人也能利用它們。為此你會寫出完善的測試或文檔,以免別人問你太多問題。
想象一下,將測試軟件的繁重工作全部外包給機器。你是開發(fā)工程師呀,這個時代最偉大的腦力工作者啊!你知道人類在處理重復性任務的時候都很糟糕,但是你還知道_機器_非常非常擅長復雜的重復性任務。更專業(yè)的開發(fā)人員就是會使用計算機來做自動化測試 —— 一整天都在綿綿不休地進行,幫你處理這些測試軟件的繁重工作。
- 自動化測試是專業(yè)的。
- 自動化測試是你的后盾,是你的肌肉。
- 自動化測試是你的秘密武器……
時不時,問一下自己這幾個問題:
- 我,還可以如何偷懶?
- 應該讓計算機幫忙測點什么?
- 計算機該在什么時候進行測試?
- 需要100%的覆蓋率嗎?
- 多少次測試就足夠了?
未完待續(xù)……
## 單元測試基礎(chǔ)
- [x] ### 單元測試與自動化的意義
- [x] ### 為什么選擇 Jest
- [x] ### Jest 的基本用法
- [x] ### 該如何測試異步代碼?
## Vue 單元測試
- [x] ### Vue 組件的渲染方式
- [x] ### Wrapper find() 方法與選擇器
- [x] ### UI 組件交互行為的測試
## Vuex 單元測試
- [x] ### CQRS 與 Redux-like 架構(gòu)
- [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 - 單元測試基礎(chǔ)
- Vue 應用單元測試的策略與實踐 01 - 前言和目標
總結(jié)
以上是生活随笔為你收集整理的vue each_Vue 应用单元测试的策略与实践 05 - 测试奖杯策略的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 网吧几个月没开门网吧几个月没开门了
- 下一篇: H3C路由器WAN口负载均衡怎么设置如何