論測(cè)試的重要性
如今程序員群體趕上了中國(guó)最龐大的農(nóng)民
群體阶捆,大街上隨便抓一把,十有八九是程序員钦听,還一個(gè)剛從某國(guó)企離職報(bào)名參加軟件培訓(xùn)班洒试。我想碼農(nóng)
的稱號(hào)或許就是這么來(lái)的吧。
在外行人看來(lái)朴上,程序員是一個(gè)成天對(duì)著電腦倒騰著代碼垒棋、看著Terminal上行云流水般的打印、過(guò)著不修邊幅的日子外加超負(fù)荷的碼農(nóng)痪宰。
在內(nèi)行人看來(lái)叼架,程序員是一個(gè)成天面對(duì)QA的"質(zhì)疑"、PM的"奪命催"以及DEVs的"吐槽"衣撬,扛著身心壓力的苦行僧
乖订。
在我看來(lái),程序員應(yīng)該是:
手持神劍具练,心懷善念乍构,胸有成竹、有理有據(jù)并且合情合理地跟QA扛点、PM哥遮、DEV斗智斗勇的戰(zhàn)士。
要擺脫QA的質(zhì)疑陵究、DEVs的吐槽以及PM的奪命催眠饮,除了那些不容易掌控的客觀因素,我們可以從自身發(fā)力铜邮,加強(qiáng)自身的"核心肌群"
君仆,呈現(xiàn)出自己的應(yīng)有的專業(yè)態(tài)度,編寫出高質(zhì)量的代碼牲距,從而促成高質(zhì)量的交付返咱。
如何交付高質(zhì)量的代碼?
首先牍鞠,我們可以擺出苦行僧的心態(tài)咖摹,平日里練就一身好把式:如Clean Code、Refactor难述、OOD及FOP萤晴。即便這樣吐句,牛逼哄哄的程序員也不敢說(shuō)自己的代碼百分之百?zèng)]有缺陷。
怎么辦店读,兩個(gè)參考原則:
- 編寫完代碼多問(wèn)自己一句:"真的可靠地完成目標(biāo)了嗎嗦枢?" 怎么問(wèn),寫個(gè)測(cè)試來(lái)提問(wèn)屯断。這便是 測(cè)試覆蓋文虏。
- 編寫代碼之前先問(wèn)自己一句:"怎么樣才算完成目標(biāo)了呢?" 怎么問(wèn)殖演,同樣寫個(gè)測(cè)試來(lái)提問(wèn)氧秘。這便是 TDD + 測(cè)試覆蓋。
測(cè)試能做什么
要知道測(cè)試能做什么趴久,首先我們需要知道測(cè)試是什么(它在測(cè)什么)丸相?它能給我們帶來(lái)什么價(jià)值?以及人力成本那么昂貴彼棍,我們?yōu)槭裁催€要花時(shí)間去編寫這些上不了產(chǎn)品的測(cè)試代碼灭忠?
程序員總喜歡倒騰點(diǎn)代碼來(lái)開(kāi)始一個(gè)話題:
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"));
}
}
這一小段測(cè)試代碼所做的事情是在驗(yàn)證StringUtils#toUpperCase
方法的功能正確性。
順便用一句話來(lái)形容單元測(cè)試:
開(kāi)發(fā)人員編寫一小段代碼座硕,用于檢驗(yàn)被檢測(cè)代碼的一個(gè)很小的更舞、很明確的功能是否正確。
廣義上的測(cè)試并不總是像上面這段代碼這么簡(jiǎn)單坎吻,熟為人知的 測(cè)試金字塔 將測(cè)試分為三大類缆蝉,單元測(cè)試位于測(cè)試金字塔底端,旨在傳達(dá)單元測(cè)試應(yīng)該來(lái)得更兇猛一些瘦真,而它們正是由開(kāi)發(fā)人員親手編寫出來(lái)刊头。本文也是圍繞單元測(cè)試來(lái)開(kāi)展。
測(cè)試的價(jià)值何在
經(jīng)常聽(tīng)開(kāi)發(fā)人員說(shuō):"我對(duì)我的代碼非常有信心诸尽。"理由往往充分且單一:單元測(cè)試是老大原杂,老大罩著我不怕。(當(dāng)然您机,專業(yè)的QA始終能發(fā)現(xiàn)DEV很難察覺(jué)到的Defect穿肄,難免會(huì)驚起一臉狐疑:老大不靈了嗎!回首代碼际看,覺(jué)漏某一Case)咸产。
所以單元測(cè)試能夠增強(qiáng)你寫代碼的信心。都說(shuō)自信是成功者必不可少的特質(zhì)仲闽。當(dāng)你對(duì)代碼充滿信心之后脑溢,你的潛能無(wú)形中被激發(fā)(你會(huì)發(fā)現(xiàn)你敲代碼的速度都會(huì)變快),這樣你工作效率的提高促使你更加輕松地完成工作赖欣。身心受益便會(huì)產(chǎn)生一連串良性的"蝴蝶效應(yīng)"屑彻。
測(cè)試的兩個(gè)無(wú)形價(jià)值就體現(xiàn)出來(lái)了:
1. 增強(qiáng)我們寫代碼的信心验庙。
2. 讓我們更加輕松的完成工作,身心收益社牲。
再來(lái)說(shuō)說(shuō)有形的代碼粪薛。缺陷減少了則證明你的代碼質(zhì)量提高了,代碼質(zhì)量衡量指標(biāo)總離不開(kāi)可讀性
搏恤、可擴(kuò)展性
违寿、可維護(hù)性
。這三個(gè)指標(biāo)的增強(qiáng)反映了良好的代碼整潔度挑社、OO設(shè)計(jì)、模塊化等巡揍。實(shí)踐證明痛阻,這些良好的設(shè)計(jì)往往不是一蹴而就的,而當(dāng)你為一個(gè)類或方法編寫單元測(cè)試卻舉步維艱的時(shí)候腮敌,你就應(yīng)該考慮去改良你的設(shè)計(jì)了阱当。
理想情況下,編寫完的代碼應(yīng)該是可以工作的糜工。但現(xiàn)實(shí)并不那么美好弊添,當(dāng)你在驗(yàn)證代碼正確性的時(shí)候遇到問(wèn)題,你就不得不頻繁地啟用調(diào)式模式捌木,而調(diào)試正是吞噬你寶貴時(shí)間的惡魔油坝。此時(shí)我們要拔出單元測(cè)試這把神劍,使出渾身解數(shù)將惡魔驅(qū)趕到塵封的黑暗角落刨裆,從而縮減我們花在調(diào)式上的時(shí)間澈圈。
那么,測(cè)試的兩個(gè)有形的價(jià)值也體現(xiàn)出來(lái)了:
3. 改良我們的設(shè)計(jì)帆啃。
4. 縮減我們花在調(diào)式上的時(shí)間瞬女。
在敏捷開(kāi)發(fā)領(lǐng)域,文檔(需求文檔努潘,詳細(xì)設(shè)計(jì)文檔等)是罕見(jiàn)之物诽偷。當(dāng)一個(gè)新人半途加入項(xiàng)目的時(shí)候,在沒(méi)有太多文檔的情況下疯坤,閱讀測(cè)試代碼便是一個(gè)很好的開(kāi)始报慕。當(dāng)然,前提是我們的測(cè)試代碼必須是可靠的压怠,并且具有良好的可讀性卖子。單元測(cè)試的第五項(xiàng)不可小覷的價(jià)值就被體現(xiàn)出來(lái):
5. 測(cè)試即文檔。
不寫測(cè)試又如何
有一種聲音:"單元測(cè)試代碼寫得再漂亮刑峡,也終究不是產(chǎn)品代碼洋闽,在部署到生產(chǎn)環(huán)境時(shí)會(huì)被無(wú)情的拋棄掉玄柠!"
所以被這種聲音迷惑的人開(kāi)始信奉了長(zhǎng)(測(cè))話(試)短(少)說(shuō)(寫),短(甚)話(至)不(不)說(shuō)(寫)的信仰诫舅。這只是經(jīng)過(guò)修飾得以傳播的一種聲音羽利,而背后做支撐的總有那么幾大派系。
無(wú)辜派
1. 我并不清楚代碼的行為刊懈,你叫我怎么測(cè)試呢这弧?
2. 這些代碼命名都能夠通過(guò)編譯啊虚汛!
個(gè)性派
1. 測(cè)試代碼不是我的工作匾浪,這不應(yīng)該由專門的人去做嗎?
2. 公司請(qǐng)我來(lái)是為了寫代碼卷哩,而不是寫測(cè)試的蛋辈。
同理派
1. 如果我讓QA人員沒(méi)有工作,那么我會(huì)覺(jué)得很內(nèi)疚的将谊!
仔細(xì)推敲這三大派系冷溶,甩出幾個(gè)問(wèn)題就能讓這些借口不攻自破:
1. 如果連代碼的行為都不清楚,寫出來(lái)的代碼意義何在尊浓?
2. 通過(guò)編譯就代表能正常工作嗎逞频?
3. 你可以不寫測(cè)試,但你寫的代碼不斷被QA找出Defect栋齿,作為DEV名聲信譽(yù)何在苗胀,難道寫出可靠的代碼也不是你的職責(zé)嗎?
4. 公司的確不是雇你來(lái)寫測(cè)試的瓦堵,那公司是顧你來(lái)調(diào)試bug的嗎柒巫?
5. 試問(wèn)QA會(huì)喜歡一個(gè)交付的代碼存在很多Defect的DEV嗎?我想QA也寧愿代碼可靠到讓他ta"無(wú)事可做"谷丸,從而去做一些功能測(cè)試堡掏、性能測(cè)試、驗(yàn)收測(cè)試等刨疼。
讓我覺(jué)得值得一提的是常規(guī)派的看法:
1. 編寫單元測(cè)試太花時(shí)間了泉唁,項(xiàng)目結(jié)束時(shí)再說(shuō)吧!
2. 運(yùn)行測(cè)試時(shí)間太長(zhǎng)了揩慕!
"編寫單元測(cè)試太花時(shí)間了亭畜,等測(cè)試結(jié)束后再說(shuō)" 聽(tīng)起來(lái)是一個(gè)很合乎情理的想法。而在軟件開(kāi)發(fā)項(xiàng)目上存在一個(gè)這樣的魔咒:
一推再推的事情迎卤,往往都是不會(huì)去做的事情拴鸵。
不去做的原因可能是重視度不夠,被和諧掉了,也可能是最后想去做也沒(méi)有時(shí)間去做劲藐。不管出于什么原因八堡,不寫測(cè)試存在潛在的風(fēng)險(xiǎn)。
實(shí)踐證明聘芜,隨著時(shí)間推移兄渺,產(chǎn)品的功能性的變化趨勢(shì)受測(cè)試代碼編寫的時(shí)機(jī)的影響如下圖所示:
好想法抵擋不住現(xiàn)實(shí)的打擊,代碼庫(kù)隨著項(xiàng)目的進(jìn)展越發(fā)復(fù)雜汰现,由于沒(méi)有測(cè)試的保護(hù)挂谍,一些不良的設(shè)計(jì)偷偷溜了進(jìn)來(lái),代碼越發(fā)嬌氣瞎饲,慢慢地沒(méi)有人敢去動(dòng)它口叙。最糟糕的結(jié)果可能是,DEVs頂著巨大交付壓力嗅战,唯唯諾諾的寫著代碼妄田,而災(zāi)難正在醞釀,交付最終失敗仗哨。
所以形庭,只有當(dāng)測(cè)試代碼并行于產(chǎn)品代碼铅辞,甚至可以采用TDD厌漂。測(cè)試的幾大價(jià)值才有可能被體現(xiàn)出來(lái),從而能夠?yàn)槲覀兊漠a(chǎn)品保駕護(hù)航斟珊。
就我個(gè)人經(jīng)驗(yàn)苇倡,半TDD的編碼方式,在一個(gè)Story上所花的總時(shí)間不會(huì)多余沒(méi)有測(cè)試裸奔的代碼囤踩≈冀罚或許剛開(kāi)始會(huì)覺(jué)得有點(diǎn)拖慢節(jié)奏,操練多了堵漱,它的威力就會(huì)彰顯出來(lái)了综慎。
測(cè)試也寫了,可是運(yùn)行時(shí)間太長(zhǎng)了又帶來(lái)了另一個(gè)苦惱勤庐?
細(xì)談該苦惱可以單獨(dú)寫一篇文章了示惊。我的確見(jiàn)過(guò)測(cè)試運(yùn)行時(shí)間很長(zhǎng),每次驗(yàn)證一次跑上半個(gè)多小時(shí)愉镰。下面列舉一些測(cè)試加速的實(shí)踐:
1. 編寫更多的單元代碼來(lái)代替一些不重要的集成測(cè)試和UI測(cè)試米罚。
2. 使用Mockito、JMock等工具模擬掉依賴丈探。
3. 并行運(yùn)行測(cè)試录择,前提是讓測(cè)試之間保持相互獨(dú)立。
4. 讓CI服務(wù)器去跑更耗時(shí)的集成測(cè)試和UI測(cè)試。
5. 使用契約測(cè)試來(lái)代替微服務(wù)之間的集成測(cè)試隘竭。
單元測(cè)試運(yùn)行時(shí)間是毫秒級(jí)別的塘秦,如果耗時(shí)過(guò)長(zhǎng),你就要留意是否存在內(nèi)存泄漏货裹、資源未釋放嗤形、依賴過(guò)重或者不依賴容器而啟動(dòng)了容器的單元測(cè)試。
揮之不去的例外
編寫單元測(cè)試是一項(xiàng)成本低卻價(jià)值很高的活動(dòng)弧圆。編寫它不會(huì)花掉你太多的時(shí)間赋兵,而運(yùn)行它更是毫秒間的事情。極限編程推崇者正在使用TDD的方式詮釋著單元測(cè)試的價(jià)值和意義搔预。
它能帶給我們信心霹期,改良我們的代碼設(shè)計(jì),提升我們(DEVs)的聲譽(yù)拯田,為代碼庫(kù)保駕護(hù)航历造,為高質(zhì)量的軟件交付提供保障。但它終究不是一顆銀彈船庇。我們編寫單元測(cè)試也無(wú)非是一種價(jià)值的取舍吭产,當(dāng)它給我們帶來(lái)的價(jià)值低于我們付出的成本時(shí),我們就要保持警惕了鸭轮,比如思考以下兩個(gè)問(wèn)題:
1. 在追求漂亮的測(cè)試覆蓋率數(shù)字100%的時(shí)候臣淤,思考一下它真有那么高的價(jià)值嗎?
2. 在做快速的技術(shù)Spike(技術(shù)調(diào)研)窃爷,思考一下不寫測(cè)試是不是能讓我更快的試錯(cuò)邑蒋?
我們要理解的是單元測(cè)試背后的核心價(jià)值,從而做出正確的取舍按厘。我們要做的是編寫出有效的單元測(cè)試医吊,讓它真正地為我們創(chuàng)造價(jià)值。
注釋
- Terminal:命令行終端
- QA:專職測(cè)試人員
- PM:項(xiàng)目經(jīng)理
- DEV:開(kāi)發(fā)人員逮京,DEVs表示復(fù)數(shù)
- OOD:面向?qū)ο笤O(shè)計(jì)
- FOP:函數(shù)時(shí)編程
- TDD:測(cè)試驅(qū)動(dòng)開(kāi)發(fā)
- CI:持續(xù)集成