提高單元測試質(zhì)量的低代碼思路(如何提高單元測試覆蓋率)
摘要
單元測試是保障代碼質(zhì)量的一個(gè)非常有效的手段。但在單元測試實(shí)踐中,往往會出現(xiàn)研發(fā)人員不愿意寫單元測試,或者即使寫了單元測試,但是其代碼質(zhì)量和覆蓋率不高的問題。本文會嘗試分析單元測試推廣難,質(zhì)量差的原因,并給出一個(gè)解決之道。
單測推廣難點(diǎn)
研發(fā)人員為什么還是不愿意寫單測呢?常見的主要有以下幾個(gè)原因:
- 雖然這代碼是我寫的,但是~~質(zhì)量不好,~~很難測
- 因?yàn)檫@代碼是我接手的,所以質(zhì)量不好,很難測
- 我只改了1行
- 寫單測要花很多時(shí)間,甚至比寫業(yè)務(wù)代碼的時(shí)間還長,值嗎?
- 業(yè)務(wù)代碼經(jīng)常變,之前寫好的單測總不過,時(shí)間緊迫,只能先注釋掉assert,犧牲單測質(zhì)量
- 項(xiàng)目排期很緊張,在既要又要還要的情況下,只能不寫單測,后期也沒時(shí)間補(bǔ)
上面1,2,3,4歸根結(jié)底是代碼質(zhì)量問題,既代碼的可測試性不好,很難解決。5,6是項(xiàng)目管理問題,如果管理的質(zhì)量意識到位,能給足研發(fā)進(jìn)行單測的時(shí)間,是可以解決的。反之如果管理層面不愿意留出單測時(shí)間,那其實(shí)說明質(zhì)量問題不是大問題。
常見解決辦法
要體現(xiàn)單測的質(zhì)量保障效果,需要從單測質(zhì)量和數(shù)量兩方面入手,那么如何提升單測的質(zhì)量和數(shù)量?
常見的提高質(zhì)量的做法就是培訓(xùn),比如通過培訓(xùn)分享單測寫的好的人的優(yōu)秀經(jīng)驗(yàn)。我就在公司內(nèi)部給大家分享過幾次單元測試100%覆蓋技巧。
而要提高單測數(shù)量,除了研發(fā)自己寫單測外,還有一種做法是將一部分單測開發(fā)工作移交給測試。但白盒測試一般都比較少,為了加大白盒測試人員的比例,也有通過培訓(xùn)黑盒測試成為白盒測試的做法。
當(dāng)然相應(yīng)的輔助管理手段也是必要的,比如定期對單元測試代碼進(jìn)行抽查,對質(zhì)量高的做表彰,對質(zhì)量低的做提醒等等。
但其實(shí)我對通過測試人員解決單測問題是有所保留的。如果專業(yè)的程序員都不想寫也寫不好單測,測試人員就可以嗎?
萬萬沒想到我也要學(xué)編程
其實(shí)不管是單側(cè)的質(zhì)量還是數(shù)量,提高的關(guān)鍵都是代碼的質(zhì)量,特別是可測試性。不解決代碼的可測試性,再怎么折騰都沒啥用。
如何提升代碼質(zhì)量?去學(xué)習(xí)OOAD,設(shè)計(jì)模式,各種編程設(shè)計(jì)原則,層出不窮的方法論嗎?捫心自問,我們真的學(xué)會并能靈活運(yùn)用這些知識,寫出高質(zhì)量代碼嗎?如果回答是的,我們今天干嘛還在討論這個(gè)問題。這些手段就其傳授的知識而言本身沒有問題,但這些手段的效率很低,周期很長,無法短期見效。學(xué)會這些你能成為一個(gè)高手,但無論從管理還是成本角度考慮,你能想象一個(gè)全是高手的團(tuán)隊(duì)嗎。
既然做法都不是可以快速復(fù)制的工業(yè)化解決方案,我們必須另辟蹊徑。從研發(fā)工程實(shí)踐來講,需要能夠快速提升批量人員代碼質(zhì)量的手段。
低代碼解決方案
想要快速提升代碼質(zhì)量,本質(zhì)上是如何低成本產(chǎn)出高質(zhì)量設(shè)計(jì)。只有這樣才能解決代碼可測試性差的問題,才有可能提升單測質(zhì)量,此外還可以思考如何減少必要代碼量。提高單測數(shù)量的本質(zhì)是希望覆蓋更多的被測代碼。如果能減少必要代碼量,則單測工作量也會相應(yīng)減少,同等人力投入下就可以覆蓋更多的被測代碼。
解決問題的辦法在問題發(fā)生的層面往往是找不到的,要實(shí)現(xiàn)上面的目標(biāo),眼光要超越代碼層面。近年來興起的低代碼無代碼技術(shù)就是一個(gè)可行的方案。但該技術(shù)爭議也比較大,我認(rèn)為主要原因是大多數(shù)低代碼工具更傾向于低門檻使用者,而忽略了專業(yè)研發(fā)的使用場景,這篇文章中專門做了分析:低代碼工具選項(xiàng)難題淺析。
X-Series是為后端研發(fā)打造的一套開源低代碼框架,可以實(shí)現(xiàn)上面的目標(biāo)。下面先簡要介紹X-Series,再分析為什么X-Series可以低成本產(chǎn)出高質(zhì)量設(shè)計(jì)和減少必要代碼量。
X-Series簡介
X-Series 是面向后端研發(fā)人員的低代碼工具,用于高效構(gòu)建可理解,可維護(hù)的后臺應(yīng)用。它包含 3 個(gè)獨(dú)立工具,分別對應(yīng)流程處理,邏輯判斷和狀態(tài)管理。
xUnit利用流程圖抽象后臺流程處理。開發(fā)時(shí)根據(jù)需求對應(yīng)的處理流程先創(chuàng)建流程圖,這一般只花1分鐘時(shí)間。流程圖畫好了,設(shè)計(jì)工作基本上也就結(jié)束了。研發(fā)既可以拿著這個(gè)流程圖去和需求方review,也可以用于和團(tuán)隊(duì)成員進(jìn)行溝通,還可以在測試階段協(xié)助白盒測試人員設(shè)計(jì)測試案例。
流程圖設(shè)計(jì)完畢后,接下來的只需要為每個(gè)流程節(jié)點(diǎn)提供代碼實(shí)現(xiàn)。不同的節(jié)點(diǎn)類型需要實(shí)現(xiàn)不同的預(yù)定義接口。一般常見的接口為以下4種:
- Processor:對傳入?yún)?shù)進(jìn)行處理,無返還結(jié)果
- Converter:對傳入?yún)?shù)進(jìn)行轉(zhuǎn)換,返回一個(gè)結(jié)果
- Validator:對傳入?yún)?shù)進(jìn)行判斷,返回true/false
- Locator:對傳入?yún)?shù)進(jìn)行判斷,返回一個(gè)key
所有的傳入?yún)?shù)類型均為Context類型。在系統(tǒng)中調(diào)用流程圖也很方便。
xUnit非常注重使用體驗(yàn),流程圖的設(shè)計(jì)和代碼的開發(fā)都可以在同一個(gè)IDE中完成。利用集成在 IDE 中的可視化編輯器,研發(fā)人員可以隨時(shí)從節(jié)點(diǎn)跳轉(zhuǎn)到代碼,有了 xUnit 相當(dāng)于有了一張系統(tǒng)藍(lán)圖,而不會迷失在代碼海洋中。
xDecision 使用決策樹模型來表達(dá)復(fù)雜邏輯判斷。用戶首先在模型中定義用于判斷的變量和決策,需要的話還可以定義類型以方便和代碼匹配。其次在圖上添加節(jié)點(diǎn),節(jié)點(diǎn)可以包含決策,也可以包含表達(dá)式用于進(jìn)一步的判斷。最后將節(jié)點(diǎn)連接起來并指定表達(dá)式,即可實(shí)現(xiàn)判斷邏輯。
使用決策樹僅需要創(chuàng)建模型實(shí)例并傳入?yún)?shù),不需要單獨(dú)生成代碼。對決策樹的創(chuàng)建維護(hù)完全在模型中,如果事實(shí)變量和決策集合沒有改變,則模型變更時(shí)無需改動主程序。xDecision 還支持生成測試代碼,用于快速檢驗(yàn)?zāi)P偷恼_性。
xState 使用狀態(tài)機(jī)模型來構(gòu)建業(yè)務(wù)對象的狀態(tài)模型,支持狀態(tài)變遷校驗(yàn)和事件處理調(diào)用。無論業(yè)務(wù)模型的狀態(tài)有多復(fù)雜,都可以用狀態(tài)圖清晰的表達(dá)。同 xDecision 類似,如果事件和狀態(tài)集合沒有變化,修改模型后無需改變調(diào)用程序。
X-Series已經(jīng)開源多年,在多個(gè)公司都有應(yīng)用。其中xUnit和xState已經(jīng)被我們公司多個(gè)研發(fā)部門采用,用戶反饋能夠很顯著的提升系統(tǒng)可理解性,提高組件復(fù)用性和研發(fā)效率。
提升可測試性的原理
在做進(jìn)一步闡述之前,我們先回顧一下什么是設(shè)計(jì)。所謂設(shè)計(jì),本質(zhì)上是一種系統(tǒng)分解的手段,既如何將一個(gè)系統(tǒng)分解為一系列相關(guān)的更小的系統(tǒng),持續(xù)這一分解的過程,直到達(dá)到能夠直接編碼實(shí)現(xiàn)的粒度。此粒度上的代碼要能滿足高內(nèi)聚,低耦合的質(zhì)量要求。
為什么說X-Series能夠提高代碼的可測試性?因?yàn)樗軌蛲瑫r(shí)滿足低成本產(chǎn)出高質(zhì)量設(shè)計(jì)和減少必要代碼量兩個(gè)要求。
以xUnit為例,流程圖是研發(fā)人員最熟悉的工具,人人都懂,利用流程圖可以將系統(tǒng)以清晰的方式展現(xiàn)出來,并且將工作分解到節(jié)點(diǎn)級別。因?yàn)榱鞒虉D是基礎(chǔ)知識,不需要熟練掌握OOAD,設(shè)計(jì)模式或其他任何設(shè)計(jì)理念就可以使用,所以利用流程圖就滿足了低成本的要求。如果流程圖的每個(gè)節(jié)點(diǎn)如果還是比較抽象復(fù)雜,則可以通過子圖的方式繼續(xù)分解。這不但保障了設(shè)計(jì)手段的一致性,而且由于不需要引入額外的設(shè)計(jì)手段,也就避免了相關(guān)培訓(xùn)成本。
流程圖本身具備的清晰易懂的特性滿足了高質(zhì)量的要求。當(dāng)流程圖分解到節(jié)點(diǎn)的工作職責(zé)已經(jīng)非常單一具體,可以利用代碼直接實(shí)現(xiàn)時(shí),則很容易滿足代碼的高內(nèi)聚要求。在代碼層面,xUnit預(yù)先定義了各類型節(jié)點(diǎn)要實(shí)現(xiàn)的接口。這些接口職責(zé)高度單一,研發(fā)人員只需要為接口提供實(shí)現(xiàn)即可。在減輕用戶負(fù)擔(dān)的同時(shí)還避免了設(shè)計(jì)引入缺陷。
流程圖節(jié)點(diǎn)間的調(diào)度協(xié)調(diào)不需要單獨(dú)代碼實(shí)現(xiàn),xUnit引擎會直接利用流程圖模型本身來完成。如果說xUnit可以大幅減少粘合代碼量的話,xDecision和xState則更進(jìn)一步通過模型直接消滅了代碼實(shí)現(xiàn)的必要性,因?yàn)樗鼈兩傻哪P涂梢栽趹?yīng)用中直接調(diào)用,不需要生成任何中間代碼。
雖然使用X-Series可以低成本產(chǎn)出高質(zhì)量設(shè)計(jì),大幅減少必要代碼量,并從設(shè)計(jì)角度保障代碼層面的高內(nèi)聚。但作為一個(gè)完整的方法論我們還需要在代碼層面為實(shí)現(xiàn)低耦合提供方案,否則還是無法徹底滿足可測試性要求。
降低耦合度的技巧
在討論技巧前我們先明確為什么低耦合是提高代碼可測試性的關(guān)鍵?
例如 A 類的 a 方法依賴 B 類的 b 方法完成自身功能,b 方法可能是一次外部調(diào)用,一次數(shù)據(jù)庫請求,或者某種復(fù)雜運(yùn)算。當(dāng)A類和B類耦合度高的時(shí)候,例如A類在自己代碼內(nèi)部創(chuàng)建B類,則要為A類提供單元測試時(shí),必須間接的滿足B類的運(yùn)行條件。這就是為什么耦合度高的代碼可測試性差的原因。反之如果A類可以通過某種方式注入B類,則可以在單元測試時(shí)使用B類的mock來代替實(shí)際的B類完成對A類的測試工作。因?yàn)閙ock可以由我們提供,創(chuàng)建和使用過程都是受控的,因此耦合度低的代碼可測試性就好。
由上面的例子可知,要降低耦合度,核心就是將所有外部依賴參數(shù)化。具體做法分兩種情況:
- 如果B類實(shí)例在A類實(shí)例的生命周期內(nèi)都不需要改變,那么只需將B類實(shí)例通過A類的構(gòu)造函數(shù)注入。如果沒有構(gòu)造函數(shù),可以創(chuàng)建一個(gè)包含B類實(shí)例的構(gòu)造函數(shù)。
- 如果B類實(shí)例在A類實(shí)例的生命周期中會發(fā)生改變,那么只需要為A類創(chuàng)建一個(gè)B類實(shí)例的setter方法即可。
完成以上改造后,即可降低代碼耦合度,因此針對 A 類 a 方法的單元測試的全部工作是:
- 把 B 類通過構(gòu)造函數(shù)或 setter 從內(nèi)部屬性變?yōu)橥獠恳蕾?/span>
- 對 B 類做 mock,重載 b 方法使其能返回特定測試所需要的值
- 通過構(gòu)造函數(shù)將 B 的 mock 注入到 A 類中
- 基于重載的 b 方法來實(shí)現(xiàn) a 方法的單元測試
一個(gè)合格的單元測試應(yīng)該要做到所有邏輯路徑 100%覆蓋,除非是特別簡單的代碼,例如標(biāo)準(zhǔn)的 getter/setter 外。有了完備的單元測試,我們就能快速檢測被測代碼本身行為是否符合預(yù)期,對系統(tǒng)其它部分是否造成影響,或者被其他部分的修改影響到。
結(jié)論
如果做一件事情特別費(fèi)力,往往是方法不對。通過提升個(gè)人能力或運(yùn)用強(qiáng)制手段提高單測質(zhì)量效果并不好。而借助低代碼,不但能低成本產(chǎn)出高質(zhì)量設(shè)計(jì),還能大幅降低代碼量,再結(jié)合提升可測試性的技巧,三管齊下就能有效提高單測覆蓋率和質(zhì)量。
作者簡介
pure 50 cents,信也技術(shù)架構(gòu)研究員,專注于中間件,低代碼,運(yùn)維平臺等方面
來源:微信公眾號:拍碼場
出處:https://mp.weixin.qq.com/s/Jzpt4Cv5ffYsDCRP-4P5eQ