一枚程序員眼中的單元測試

論測試的重要性

如今程序員群體趕上了中國最龐大的農(nóng)民群體,大街上隨便抓一把,十有八九是程序員,還一個剛從某國企離職報名參加軟件培訓班。我想碼農(nóng)的稱號或許就是這么來的吧。

在外行人看來,程序員是一個成天對著電腦倒騰著代碼、看著Terminal上行云流水般的打印、過著不修邊幅的日子外加超負荷的碼農(nóng)。

在內(nèi)行人看來,程序員是一個成天面對QA的"質(zhì)疑"、PM的"奪命催"以及DEVs的"吐槽",扛著身心壓力的苦行僧

在我看來,程序員應該是:

手持神劍,心懷善念,胸有成竹、有理有據(jù)并且合情合理地跟QA、PM、DEV斗智斗勇的戰(zhàn)士。

要擺脫QA的質(zhì)疑、DEVs的吐槽以及PM的奪命催,除了那些不容易掌控的客觀因素,我們可以從自身發(fā)力,加強自身的"核心肌群",呈現(xiàn)出自己的應有的專業(yè)態(tài)度,編寫出高質(zhì)量的代碼,從而促成高質(zhì)量的交付。

如何交付高質(zhì)量的代碼?

首先,我們可以擺出苦行僧的心態(tài),平日里練就一身好把式:如Clean Code、Refactor、OOD及FOP。即便這樣,牛逼哄哄的程序員也不敢說自己的代碼百分之百沒有缺陷。

怎么辦,兩個參考原則:

  • 編寫完代碼多問自己一句:"真的可靠地完成目標了嗎?" 怎么問,寫個測試來提問。這便是 測試覆蓋。
  • 編寫代碼之前先問自己一句:"怎么樣才算完成目標了呢?" 怎么問,同樣寫個測試來提問。這便是 TDD + 測試覆蓋。

測試能做什么

要知道測試能做什么,首先我們需要知道測試是什么(它在測什么)?它能給我們帶來什么價值?以及人力成本那么昂貴,我們?yōu)槭裁催€要花時間去編寫這些上不了產(chǎn)品的測試代碼?

程序員總喜歡倒騰點代碼來開始一個話題:

public class StringUtils {
    public static String toUpperCase(String source) {
        if (source == null) {
            return null;
        }
        return source.toUpperCase();
    }
}


class StringUtilsTest {
    @Test
    void convert_to_upper_case() throws Exception {
        assertThat(StringUtils.toUpperCase("unit-test"), is("UNIT-TEST"));
    }
}

這一小段測試代碼所做的事情是在驗證StringUtils#toUpperCase方法的功能正確性。

順便用一句話來形容單元測試:

開發(fā)人員編寫一小段代碼,用于檢驗被檢測代碼的一個很小的、很明確的功能是否正確。

廣義上的測試并不總是像上面這段代碼這么簡單,熟為人知的 測試金字塔 將測試分為三大類,單元測試位于測試金字塔底端,旨在傳達單元測試應該來得更兇猛一些,而它們正是由開發(fā)人員親手編寫出來。本文也是圍繞單元測試來開展。


測試的價值何在

經(jīng)常聽開發(fā)人員說:"我對我的代碼非常有信心。"理由往往充分且單一:單元測試是老大,老大罩著我不怕。(當然,專業(yè)的QA始終能發(fā)現(xiàn)DEV很難察覺到的Defect,難免會驚起一臉狐疑:老大不靈了嗎!回首代碼,覺漏某一Case)。

所以單元測試能夠增強你寫代碼的信心。都說自信是成功者必不可少的特質(zhì)。當你對代碼充滿信心之后,你的潛能無形中被激發(fā)(你會發(fā)現(xiàn)你敲代碼的速度都會變快),這樣你工作效率的提高促使你更加輕松地完成工作。身心受益便會產(chǎn)生一連串良性的"蝴蝶效應"。

測試的兩個無形價值就體現(xiàn)出來了:

1. 增強我們寫代碼的信心。
2. 讓我們更加輕松的完成工作,身心收益。

再來說說有形的代碼。缺陷減少了則證明你的代碼質(zhì)量提高了,代碼質(zhì)量衡量指標總離不開可讀性、可擴展性、可維護性。這三個指標的增強反映了良好的代碼整潔度、OO設計、模塊化等。實踐證明,這些良好的設計往往不是一蹴而就的,而當你為一個類或方法編寫單元測試卻舉步維艱的時候,你就應該考慮去改良你的設計了。

理想情況下,編寫完的代碼應該是可以工作的。但現(xiàn)實并不那么美好,當你在驗證代碼正確性的時候遇到問題,你就不得不頻繁地啟用調(diào)式模式,而調(diào)試正是吞噬你寶貴時間的惡魔。此時我們要拔出單元測試這把神劍,使出渾身解數(shù)將惡魔驅(qū)趕到塵封的黑暗角落,從而縮減我們花在調(diào)式上的時間。

那么,測試的兩個有形的價值也體現(xiàn)出來了:

3. 改良我們的設計。
4. 縮減我們花在調(diào)式上的時間。

在敏捷開發(fā)領域,文檔(需求文檔,詳細設計文檔等)是罕見之物。當一個新人半途加入項目的時候,在沒有太多文檔的情況下,閱讀測試代碼便是一個很好的開始。當然,前提是我們的測試代碼必須是可靠的,并且具有良好的可讀性。單元測試的第五項不可小覷的價值就被體現(xiàn)出來:

5. 測試即文檔。

不寫測試又如何

有一種聲音:"單元測試代碼寫得再漂亮,也終究不是產(chǎn)品代碼,在部署到生產(chǎn)環(huán)境時會被無情的拋棄掉!"

所以被這種聲音迷惑的人開始信奉了長(測)話(試)短(少)說(寫),短(甚)話(至)不(不)說(寫)的信仰。這只是經(jīng)過修飾得以傳播的一種聲音,而背后做支撐的總有那么幾大派系。

無辜派

1. 我并不清楚代碼的行為,你叫我怎么測試呢?
2. 這些代碼命名都能夠通過編譯?。?

個性派

1. 測試代碼不是我的工作,這不應該由專門的人去做嗎?
2. 公司請我來是為了寫代碼,而不是寫測試的。

同理派

1. 如果我讓QA人員沒有工作,那么我會覺得很內(nèi)疚的!

仔細推敲這三大派系,甩出幾個問題就能讓這些借口不攻自破:

1. 如果連代碼的行為都不清楚,寫出來的代碼意義何在?
2. 通過編譯就代表能正常工作嗎?
3. 你可以不寫測試,但你寫的代碼不斷被QA找出Defect,作為DEV名聲信譽何在,難道寫出可靠的代碼也不是你的職責嗎?
4. 公司的確不是雇你來寫測試的,那公司是顧你來調(diào)試bug的嗎?
5. 試問QA會喜歡一個交付的代碼存在很多Defect的DEV嗎?我想QA也寧愿代碼可靠到讓他ta"無事可做",從而去做一些功能測試、性能測試、驗收測試等。

讓我覺得值得一提的是常規(guī)派的看法:

1. 編寫單元測試太花時間了,項目結束時再說吧!
2. 運行測試時間太長了!

"編寫單元測試太花時間了,等測試結束后再說" 聽起來是一個很合乎情理的想法。而在軟件開發(fā)項目上存在一個這樣的魔咒:

一推再推的事情,往往都是不會去做的事情。

不去做的原因可能是重視度不夠,被和諧掉了,也可能是最后想去做也沒有時間去做。不管出于什么原因,不寫測試存在潛在的風險。

實踐證明,隨著時間推移,產(chǎn)品的功能性的變化趨勢受測試代碼編寫的時機的影響如下圖所示:

好想法抵擋不住現(xiàn)實的打擊,代碼庫隨著項目的進展越發(fā)復雜,由于沒有測試的保護,一些不良的設計偷偷溜了進來,代碼越發(fā)嬌氣,慢慢地沒有人敢去動它。最糟糕的結果可能是,DEVs頂著巨大交付壓力,唯唯諾諾的寫著代碼,而災難正在醞釀,交付最終失敗。

所以,只有當測試代碼并行于產(chǎn)品代碼,甚至可以采用TDD。測試的幾大價值才有可能被體現(xiàn)出來,從而能夠為我們的產(chǎn)品保駕護航。

就我個人經(jīng)驗,半TDD的編碼方式,在一個Story上所花的總時間不會多余沒有測試裸奔的代碼?;蛟S剛開始會覺得有點拖慢節(jié)奏,操練多了,它的威力就會彰顯出來了。

測試也寫了,可是運行時間太長了又帶來了另一個苦惱?

細談該苦惱可以單獨寫一篇文章了。我的確見過測試運行時間很長,每次驗證一次跑上半個多小時。下面列舉一些測試加速的實踐:

1. 編寫更多的單元代碼來代替一些不重要的集成測試和UI測試。
2. 使用Mockito、JMock等工具模擬掉依賴。
3. 并行運行測試,前提是讓測試之間保持相互獨立。
4. 讓CI服務器去跑更耗時的集成測試和UI測試。
5. 使用契約測試來代替微服務之間的集成測試。

單元測試運行時間是毫秒級別的,如果耗時過長,你就要留意是否存在內(nèi)存泄漏、資源未釋放、依賴過重或者不依賴容器而啟動了容器的單元測試。


揮之不去的例外

編寫單元測試是一項成本低卻價值很高的活動。編寫它不會花掉你太多的時間,而運行它更是毫秒間的事情。極限編程推崇者正在使用TDD的方式詮釋著單元測試的價值和意義。

它能帶給我們信心,改良我們的代碼設計,提升我們(DEVs)的聲譽,為代碼庫保駕護航,為高質(zhì)量的軟件交付提供保障。但它終究不是一顆銀彈。我們編寫單元測試也無非是一種價值的取舍,當它給我們帶來的價值低于我們付出的成本時,我們就要保持警惕了,比如思考以下兩個問題:

1. 在追求漂亮的測試覆蓋率數(shù)字100%的時候,思考一下它真有那么高的價值嗎?
2. 在做快速的技術Spike(技術調(diào)研),思考一下不寫測試是不是能讓我更快的試錯?

我們要理解的是單元測試背后的核心價值,從而做出正確的取舍。我們要做的是編寫出有效的單元測試,讓它真正地為我們創(chuàng)造價值。


注釋

  • Terminal:命令行終端
  • QA:專職測試人員
  • PM:項目經(jīng)理
  • DEV:開發(fā)人員,DEVs表示復數(shù)
  • OOD:面向?qū)ο笤O計
  • FOP:函數(shù)時編程
  • TDD:測試驅(qū)動開發(fā)
  • CI:持續(xù)集成
最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容