從另一個角度告訴你單元測試的意義

當(dāng)下微服務(wù)如火如荼供搀,各個團(tuán)隊(duì)在爭先恐后推出微服務(wù)郊供,不論在概念上還是在實(shí)踐上,如果自己沒有跟微服務(wù)掛上鉤,便會被貼上落伍的標(biāo)簽盐股。我們在推微服務(wù)的時候钱豁,我們說微服務(wù)架構(gòu)具備如下優(yōu)勢:

  • 架構(gòu)靈活,能夠應(yīng)對復(fù)雜的業(yè)務(wù)需求疯汁。
  • 獨(dú)立部署牲尺,大大提高CI/CD的效率。
  • 服務(wù)自治幌蚊,支持技術(shù)棧多元化谤碳。
  • ......

這些特征恰恰是單點(diǎn)應(yīng)用無法具備的,因此微服務(wù)架構(gòu)在廣大的呼聲下逐漸承接了單點(diǎn)應(yīng)用的替代工作溢豆。隨著容器技術(shù)的成熟蜒简,使用Docker重建一個應(yīng)用的成本趨近于零。而K8S/Rancher在生產(chǎn)上的廣泛應(yīng)用漩仙,很大程度解決了容器管理的難題搓茬。調(diào)用鏈分析工具(ZipKin)、ELK+Kibana再配合系統(tǒng)監(jiān)控工具(Prometheus)队他,就連微服務(wù)架構(gòu)帶來的部署運(yùn)維的復(fù)雜性也得到了大大的改善卷仑。更加樂觀的是,眾多云平臺(AWS, GCP, Azure等)正在試圖打造解決部署運(yùn)維難題的一體化Paas服務(wù)漱挎,讓應(yīng)用開發(fā)商更加專注于業(yè)務(wù)上系枪。

如果將軟件生命周期大致劃分成兩部分:

我們認(rèn)為左邊部分正在享受著微服務(wù)架構(gòu)的益處,而右邊部分在遭受著微服務(wù)架構(gòu)復(fù)雜性的折磨磕谅。

微服務(wù)架構(gòu)帶來的復(fù)雜性(右邊部分)已經(jīng)很大程度上得到了解決私爷,常見的解決方案是在開發(fā)團(tuán)隊(duì)中植入DEVOPS。比如在ThoughtWorks中的某些團(tuán)隊(duì)膊夹,DEVOPS成為Team不可或缺的成分衬浑。

我們把注意力放在左邊部分。開發(fā)人員關(guān)注更多的是開發(fā)放刨,每個服務(wù)由一個小的Team負(fù)責(zé)開發(fā)工秩,Team正在極力往服務(wù)自治的方向靠攏。測試人員可能更加關(guān)注測試进统,尤其是契約測試伴隨著業(yè)界對集成測試(UI測試)的痛斥聲而崛起助币。消費(fèi)者驅(qū)動契約測試的演講比比皆是,我也沒有例外螟碎,在某Account的技術(shù)大會上做了一次 [微服務(wù)架構(gòu)下的測試應(yīng)對策略]({{ site.url }}{{ '/test-strategy-to-meet-microservice-architecture/' }}) 的分享眉菱。在分享中,我趕時髦提倡用契約測試取代集成測試掉分,但是細(xì)節(jié)中沒有忽略的一個核心點(diǎn):單元測試俭缓。這也是本文我要分享的重點(diǎn)克伊。


基本最無敵

單元測試是根,是基本华坦,基本最無敵

單元測試存在于測試金字塔的底端愿吹,撐起了整個金字塔,編寫它是開發(fā)人員的職責(zé)惜姐。微服務(wù)架構(gòu)讓服務(wù)更加獨(dú)立小巧犁跪,這意味著我們不用為小巧的代碼庫編寫單元測試了嗎?微服務(wù)架構(gòu)提倡服務(wù)與服務(wù)之間通過契約測試來集成歹袁,這意味著我們只用編寫契約測試就足夠了嗎耘拇?

假設(shè)我們以正確的姿勢在踐行微服務(wù)的相關(guān)技術(shù)實(shí)踐。

CI上會伴隨每次提交都觸發(fā)單元測試宇攻、Service測試(API測試)、契約測試倡勇,所有測試通過后開始獨(dú)立部署逞刷,如果我們的契約測試寫的足夠好,便可以自信地獨(dú)立部署妻熊。如果Service測試覆蓋的足夠全夸浅,便可以自信地說代碼缺陷率很低。此時我們可能認(rèn)為單元測試業(yè)務(wù)價值低扔役,不必過多關(guān)注帆喇。

回到現(xiàn)實(shí),實(shí)際情況可能是這樣子的亿胸。

CI上有契約測試的Stage坯钦,但也是草率編寫,甚至契約測試因?yàn)闆]人維護(hù)而被默認(rèn)忽略侈玄。Service測試寫了大量的Case婉刀,導(dǎo)致測試運(yùn)行時間被拖長,Build效率大大降低序仙。由于大量的Servcie測試的存在導(dǎo)致單元測試被過度輕視突颊,再加上無效的測試充斥著代碼庫。這幾點(diǎn)不但扼殺了服務(wù)獨(dú)立部署 的特性潘悼,而且增加了開發(fā)部署的工作量律秃。

再者,根據(jù)康威定律:系統(tǒng)架構(gòu)取決于組織架構(gòu)治唤,它倆息息相關(guān)棒动。不少團(tuán)隊(duì)?wèi)汛⒎?wù)架構(gòu)的夢想,卻在老一套組織架構(gòu)的驅(qū)使下漸行漸遠(yuǎn)肝劲。最后迫不得已迁客,將原來一個大Team按照功能模塊拆成幾個小Team郭宝,將代碼庫粗暴地拆分成多個,每個開發(fā)人員同時往所有的代碼庫中提交代碼掷漱。

微服務(wù)架構(gòu)的優(yōu)勢會驅(qū)使團(tuán)隊(duì)在一開始就高架微服務(wù)粘室,無視業(yè)務(wù)需求復(fù)雜度,走一遍Event Storming卜范,來一場DDD活動衔统,確定幾個服務(wù)便開始搞下去。而微服務(wù)架構(gòu)提倡的是演進(jìn)式架構(gòu)海雪,某些團(tuán)隊(duì)卻因?yàn)楦鞣N原因一直停留在起初確定的那幾個服務(wù)下開發(fā)下去锦爵,拆分的不合理性和演進(jìn)性沒有得到體現(xiàn)。這足以扼殺微服務(wù)架構(gòu)能夠應(yīng)對復(fù)雜業(yè)務(wù)需求 的特性奥裸。

微服務(wù)架構(gòu)本身并沒有錯险掀。歸根結(jié)底是業(yè)務(wù)的復(fù)雜性很難被駕馭。我們說DDD可以幫助做微服務(wù)設(shè)計湾宙,于是我們都來學(xué)習(xí)Eric Evans 的DDD樟氢,可它卻不能有效解決以下幾個問題:

  • 如何進(jìn)行領(lǐng)域建模?
  • 如何劃分限界上下文侠鳄?
  • 如何在實(shí)現(xiàn)層面定義對象埠啃?

所以,我們學(xué)習(xí)了DDD還是不會DDD伟恶。但有一點(diǎn)毋庸置疑碴开,我們每個人(DEV)都會編寫單元測試。我們在試圖駕馭微服務(wù)架構(gòu)的路上摒棄了陳舊的集成測試博秫、掌握了新的契約測試潦牛,而任何時候我們都應(yīng)該始終抓住根本:編寫有效的單元測試來為我們的系統(tǒng)保駕護(hù)航。


三個維度看單元測試

我們不會說單元測試是靈丹妙藥罢绽,對于100%覆蓋率我們也應(yīng)該持有保留態(tài)度静盅。但在一個微服務(wù)架構(gòu)基礎(chǔ)設(shè)施還不完善、開發(fā)人員能力參差不齊蒿叠、DDD能力不足以應(yīng)對復(fù)雜業(yè)務(wù)的情況下,單元測試是性價比最高的實(shí)踐痊银。

能力建設(shè)

一個具備開發(fā)經(jīng)驗(yàn)的開發(fā)人員施绎,基本上都會編寫單元測試贞绳。即便不會致稀,可以通過培訓(xùn)來快速達(dá)成抖单。從學(xué)習(xí)曲線上看,單元測試很容易上手(方法難以被測試另當(dāng)別論)矛绘,擁抱Java大腿的JUnit就是一個很好的例子。所以在一個團(tuán)隊(duì)中羊精,我們可以過培訓(xùn)囚玫、Pair 快速讓開發(fā)人員具備編寫單元測試能力。

測試即文檔劫灶,對于新上項(xiàng)目的開發(fā)人員本昏,可以通過閱讀單元測試來了解業(yè)務(wù)需求枪汪,并且不會對一系列具備復(fù)雜數(shù)據(jù)安裝的Service測試產(chǎn)生恐懼感。

生產(chǎn)效率

在那些重Service測試而輕單元測試的項(xiàng)目中雀久,Service測試?yán)锏臄?shù)據(jù)安裝缺少易用的腳手架,實(shí)際上編寫出來的諸多Service測試猶如行尸走肉祝沸,不但沒有測試出缺陷越庇,還降低了測試運(yùn)行速度,拉長了反饋時間涩惑。

實(shí)踐證明桑驱,很多缺陷完全可以通過單元測試來發(fā)現(xiàn)跛蛋,測試金字塔提出者Martin Fowler 強(qiáng)調(diào) 如果一個高層測試失敗了痊硕,不僅僅表明功能代碼中存在bug,還意味著單元測試的欠缺此衅。因此亭螟,無論何時修復(fù)失敗的端到端測試,都應(yīng)該同時添加相應(yīng)的單元測試预烙。 而越早發(fā)現(xiàn)發(fā)現(xiàn)Bug扁掸,造成的浪費(fèi)就會越小,單元測試本身就能夠提供了快速反饋的機(jī)制谴分。

卓越態(tài)度

追求卓越是一個優(yōu)秀程序員必備的態(tài)度。優(yōu)秀的程序員除了能夠編寫Clean Code忘伞,還應(yīng)該能夠編寫Clean Test。而Clean Code的基本特征之一是易于測試。單元測試可以充當(dāng)一個設(shè)計工具瞬欧,它有助于開發(fā)人員去思考代碼結(jié)構(gòu)的設(shè)計舀奶,讓代碼更加有利于測試斋射。知名的開源代碼庫從來不會缺乏單元測試,而給與他們自信的也正是這些可觀的單元測試覆蓋率怀大。

考慮到成本與收益比呀闻,我們不必保證100%的覆蓋率。因?yàn)殡S著覆蓋率提升蓖康,單元的測試的價值越來越低,而編寫的成本卻越來越高蒜焊。所以相比于100%這個漂亮的數(shù)字泳梆,我們應(yīng)該去追求那不到100%的單元測試的有效性。


夯實(shí)根基

單元測試能為代碼庫保駕護(hù)航的前提是它本身應(yīng)該有效可靠优妙。

編寫單元測試的能力容易培養(yǎng)套硼,但編寫有效的單元測試卻需要不斷地刻意練習(xí),甚至一個有多年經(jīng)驗(yàn)的Senior開發(fā)人員也不一定能夠時刻編寫出有效的單元測試邪意。

讓單元測試有效的一個很好的方式是盡可能讓我們的被測代碼具備良好的可測性雾鬼。要做到這點(diǎn),我們需要盡可能的在編碼的過程中掌握必要的代碼設(shè)計原則策菜。就拿面向?qū)ο蟮木幊叹幊陶Z言來講做入,我們在編寫代碼的時候要時刻思考Robert C. Martin 提出的SOLID原則:

  • SRP(Single Responsibility Principle)同衣,單一職責(zé)原則
  • OCP(Open Closed Principle),開放封閉原則
  • LSP(Liskov Substitution Principle)浪秘,里氏替換原則
  • ISP(Interface Segregation Principle)埠况,接口分離原則
  • DIP(Dependency Inversion Principle),依賴倒置原則

同時我們應(yīng)該盡量避免編寫STUPID代碼:

  • Sington夺衍,單例
  • Tight Coupling喜命,緊耦合
  • Untestability河劝,不可測
  • Premature Optimization矛紫,過早優(yōu)化
  • Indescriptive Naming颊咬,胡亂命名
  • Duplication,重復(fù)代碼

在做設(shè)計和編寫代碼的時候多思考我們是不是在踐行GRASP原則:

  • Controller敞临,控制器
  • Creator杭隙,創(chuàng)造者
  • High cohesion,高內(nèi)聚
  • Low coupling票髓,低耦合
  • Polymorphism铣耘,多態(tài)
  • Indirection,中介
  • Information expert裆操,信息專家
  • Protected Variations炉媒,受保護(hù)變化
  • Pure fabrication,純虛構(gòu)

以上這些原則需要在編碼中不斷地刻意練習(xí)缎岗,除了閱讀針對性的書籍传泊,在團(tuán)隊(duì)中積極組織 Code Review鸭巴、推動 Pair 來互相學(xué)習(xí)和改進(jìn)是一個更有效的方式。

良好的代碼設(shè)計讓我們的單元測試更加容易編寫溪椎,而要編寫有效的單元測試池磁,我們應(yīng)該對以下幾個維度的測試壞味道保持敏銳的嗅覺:

  • 可讀性:基本斷言附加細(xì)節(jié)地熄、冗長安裝邏輯分隔端考、魔法數(shù)字却特、過度斷言 等。
  • 可維護(hù)性:重復(fù)椿浓、條件邏輯闽晦、參數(shù)化混亂仙蛉、殘缺路徑永久性臨時文件荠瘪、弱不禁風(fēng) 等哀墓。
  • 可靠性:被注釋篮绰、歧義注釋永不失敗輕率承諾星瘾、降低期望琳状、有條件的測試 等。

我的呼吁

我們在試圖駕馭微服務(wù)架構(gòu)的路上,應(yīng)該時刻守住根本边翁,讓單元測試這項(xiàng)成本低、收益高的實(shí)踐為我們高層測試打好地基硕盹。如何設(shè)計良好可測的代碼 以及 如何編寫有效的單元測試 更是值得每一位追求卓越的程序員去深入學(xué)習(xí)和實(shí)踐。

如果你還在思考為什要寫單元測試啊胶?推薦閱讀我的文章 一枚程序員眼中的單元測試

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末垛贤,一起剝皮案震驚了整個濱河市焰坪,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌聘惦,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,843評論 6 502
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘟仿,居然都是意外死亡劳较,警方通過查閱死者的電腦和手機(jī)观蜗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,538評論 3 392
  • 文/潘曉璐 我一進(jìn)店門墓捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來砖第,“玉大人梧兼,你說我怎么就攤上這事智听。” “怎么了?”我有些...
    開封第一講書人閱讀 163,187評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長欣喧。 經(jīng)常有香客問我唆阿,道長驯鳖,這世上最難降的妖魔是什么浅辙? 我笑而不...
    開封第一講書人閱讀 58,264評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮呼巴,結(jié)果婚禮上诊赊,老公的妹妹穿的比我還像新娘府瞄。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,289評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著像吻,像睡著了一般拨匆。 火紅的嫁衣襯著肌膚如雪惭每。 梳的紋絲不亂的頭發(fā)上台腥,一...
    開封第一講書人閱讀 51,231評論 1 299
  • 那天,我揣著相機(jī)與錄音峻汉,去河邊找鬼休吠。 笑死瘤礁,一個胖子當(dāng)著我的面吹牛柜思,可吹牛的內(nèi)容都是我干的酝蜒。 我是一名探鬼主播,決...
    沈念sama閱讀 40,116評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼途戒!你這毒婦竟也來了喷斋?” 一聲冷哼從身側(cè)響起星爪,我...
    開封第一講書人閱讀 38,945評論 0 275
  • 序言:老撾萬榮一對情侶失蹤近零,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后裙士,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體潮售,經(jīng)...
    沈念sama閱讀 45,367評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡皱埠,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,581評論 2 333
  • 正文 我和宋清朗相戀三年训枢,在試婚紗的時候發(fā)現(xiàn)自己被綠了恒界。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,754評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖虾宇,靈堂內(nèi)的尸體忽然破棺而出旭贬,到底是詐尸還是另有隱情骑篙,我是刑警寧澤,帶...
    沈念sama閱讀 35,458評論 5 344
  • 正文 年R本政府宣布脏榆,位于F島的核電站须喂,受9級特大地震影響仔役,放射性物質(zhì)發(fā)生泄漏又兵。R本人自食惡果不足惜沛厨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,068評論 3 327
  • 文/蒙蒙 一参袱、第九天 我趴在偏房一處隱蔽的房頂上張望辰企。 院中可真熱鬧牢贸,春花似錦潜索、人聲如沸誊抛。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,692評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至诈铛,卻和暖如春幢竹,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背咬荷。 一陣腳步聲響...
    開封第一講書人閱讀 32,842評論 1 269
  • 我被黑心中介騙來泰國打工唇牧, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留腔召,地道東北人。 一個月前我還...
    沈念sama閱讀 47,797評論 2 369
  • 正文 我出身青樓,卻偏偏與公主長得像舔琅,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,654評論 2 354

推薦閱讀更多精彩內(nèi)容