我在阿里做架構 | 單測最佳實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"前言","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"大家仔細思考,我們究竟爲什麼需要編寫單元測試代碼(下述簡稱單測)?站在特定的業務角度,單測並非強訴求,只要測試團隊資源足夠充裕(空間換時間),那麼寫不寫單測最終達成的效果其實是一致的,","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"這是事實","attrs":{}},{"type":"text","text":"。但是面對擁有較強專業複雜度和嚴謹性的支付業務場景,以及測試團隊資源的嚴重匱乏,註定了編寫單測代碼的必要性和強制性,單測必須寫,而且只能由開發同學來負責(畢竟你的邏輯你最熟悉),","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"這是現實","attrs":{}},{"type":"text","text":"。單測是所有測試中最底層、最基礎、最重要的一類測試,同時也是唯一能夠保證代碼覆蓋率達到100%的測試,如果開發同學能夠認真書寫單測代碼,不把其當成“負擔”,那麼我們幾乎能夠確保>=80%的代碼缺陷能夠在","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"前置","attrs":{}},{"type":"text","text":"測試環節就被發現和修復,大幅度降低因後置而導致的高昂的缺陷修復成本,就像工廠組裝手機一樣,如果各個元器件在出廠時沒有分別經過嚴格的質量檢驗,僅依靠最終組裝成品來驗證手機是否合格,那麼你敢買嗎?其次,我們習以爲常的總是喜歡把“高內聚,低耦合”掛在嘴邊,並標榜爲優秀設計的標準,但如果我們很難編寫出,或不知如何編寫出對應的單測代碼,那麼如何能夠快速驗證自己的設計是否真的優秀?而如果我們擁有高覆蓋率的單側代碼,則能夠使得開發同學擁有足夠的信心去重構自身的邏輯代碼,使系統不斷朝前進化。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"別進入誤區","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但恰恰也是因爲前置這個誤區,從某種程度上錯誤的部分開發同學把單測當成了軟件質量保證的“銀彈”,大量集成測試階段才需要實施的驗證工作被前置在了單測階段(比如:集成localdb去驗證SQL語法、語義,以及RPC驗證等),極大程度上束縛了整個產品的迭代交付速度,從而導致研發同學怨聲載道(尤其是新同學)。","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"我們需要轉變思想","attrs":{}},{"type":"text","text":",本質而言,單測是一件非常純粹的工作,要寫的輕薄,強調的是效率,必須採取小步快跑的模式。因此在編寫單測代碼時,開發同學僅需關注邏輯單元的正確性,任何超出單元範圍的測試驗證(","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"單元所定義範圍可以是目標函數,也可以是類,總之就是基於意圖所定義出的最小功能模塊","attrs":{}},{"type":"text","text":"),全部MOCK,想要跑得快,那就必須丟包袱,在合適的環節去做正確的事,不要幻想着集中在某一個點能夠解決所有問題。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"單測最佳實踐","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"單測的定義","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"剛纔我已經提及過,","attrs":{}},{"type":"text","marks":[{"type":"color","attrs":{"color":"#000080","name":"user"}},{"type":"strong","attrs":{}}],"text":"單元所定義範圍可以是目標函數,也可以是類,總之就是基於意圖所定義出的最小功能模塊,意圖很重要","attrs":{}},{"type":"text","text":"。舉個例子,假設我今天需要在支付寶的賬務系統上面新增一個緩衝記賬的優化功能來緩解熱點賬戶的問題,那麼我需要在系統入口處至持久層都編寫相應的緩衝邏輯,那麼在編寫對應的測試代碼時,如果我把單元定義爲函數,則需要爲每一個目標函數都編寫一個對應的單測代碼,儘管可以這樣做,但如果我把單元從邏輯上定義爲“相同的目標”,那麼我其實編寫一個測試函數即可,哪怕其中包含了N個方法,也可以認爲是同一個單元,並且這樣做有利於做一些關聯測試。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在某些情況下,如果我們僅僅只是修改了某個函數代碼塊中的部分邏輯,不依賴任何其它函數的調用,示例1-1:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"public void doHandle(UniformEvent uniformEvent){\n // 原有邏輯\n System.out.println(\"新增邏輯\");\n // 原有邏輯\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果我們所定義的單元就是上述目標函數,那麼僅需爲這個目標函數編寫一個對應的測試方法驗證其邏輯正確性即可。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"單測究竟應該怎麼寫?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實際上,我並不是專業的測試人員,對於單元測試、增量測試、全量測試、迴歸測試、集成測試,以及冒煙測試等不勝枚舉的測試方法及手段常常是望洋興嘆,甚至無法準確歸類。實際上,我比較贊成和認同google的做法,對於測試,簡單分爲3類,沒那麼多花樣,如圖1所示。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/03/0325382162684fa88331c74881ddf46a.png","alt":null,"title":"圖1 google軟件測試分類","style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中小型測試僅關注目標函數的邏輯性驗證,其它部分全部mock,強調的是效率;而中型測試關注的是多模塊之間的交互性驗證;最後大型測試則顯得比較重和全面,我們可以理解爲集成測試。那麼我們的單元測試究竟應該如何歸類呢?顯而易見,單元測試歸類在小型測試中是最適合不過的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"編寫優秀的單測代碼,即簡單也複雜,最主要是需要理解編寫單測究竟是爲了驗證什麼。基本來說,大家可以參考如下3項標準來編寫單測代碼:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"輸入正確/錯誤的入參,驗證邏輯代碼正確/錯誤的處理邏輯是否符合預期;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"任何外部依賴(比如:RPC依賴、DB依賴、MQ依賴)均應該Mock或Fack,由集成測試階段兜底;","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#006400","name":"user"}}],"text":"單元測試的數據應該在單測代碼中構造,不應該依賴外部存儲或中間件,確保整體測試環境的穩定(避免因某個前置方法的處理邏輯導致存儲系統出現異常,繼而影響後續的測試方法都出現異常,產生蝴蝶效應)。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於第3點,我建議將測試數據和測試代碼進行分離,在實際的開發過程中,我們往往需要構造N套測試數據基於相同的測試邏輯去驗證邏輯代碼是否符合預期,爲了方便管理和維護測試數據,我們需要將測試數據了測試代碼解耦,編寫單獨的測試方法去準備這些測試數據,示例1-2:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"java"},"content":[{"type":"text","text":"private static List datas = new ArrayList();\npublic @Test void testMethod(){\n datas.forEach(x->{\n // 測試邏輯\n });\n}\n@BeforeClass\npublic static void init(){\n // 測試數據準備\n}","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除此之外,還可以選擇存儲在配置文件中(比如:yaml、csv文件)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"====== END ======","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,本文內容全部結束。如果在閱讀過程中有任何疑問,歡迎在評論區留言參與討論。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"推薦文章:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/afb69dbad5329096215ffe9e1","title":"","type":null},"content":[{"type":"text","text":"源碼系列 | 阿里 JVM-Sandbox 核心源碼剖析","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/8304c894c4e38318d38ceb116","title":"","type":null},"content":[{"type":"text","text":"實操 | 剖析 Java16 新語法特性","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/6ad9f5278c0981de6971c70f4","title":"","type":null},"content":[{"type":"text","text":"專訪 | 我與畢玄大師的對話","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/ae514bc05af1f16b151a351f8","title":"","type":null},"content":[{"type":"text","text":"算力 | 手寫紅黑樹","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/d367c19896e4cef6fbb661cf7","title":"","type":null},"content":[{"type":"text","text":"硬核系列 | 深入剖析字節碼增強","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/cef6d2931a54f85142d863db7","title":"","type":null},"content":[{"type":"text","text":"硬核系列 | 深入剖析 Java 協程","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/61868b3f66de36d32a5f1434f","title":"","type":null},"content":[{"type":"text","text":"白玉試毒 | 灰度架構設計方案","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/655943e5f85e6f79ffbd03047","title":"","type":null},"content":[{"type":"text","text":"新時代背景下的 Java 語法特性","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/92ba88c7926b5f5c6fbc11830","title":"","type":null},"content":[{"type":"text","text":"剖析 Java15 新語法特性","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/4d571787a3280ef3094338f9b","title":"","type":null},"content":[{"type":"text","text":"看門狗 | 分佈式鎖架構設計方案 -01","attrs":{}}]}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/545a3accd173d6517ebd0ad59","title":"","type":null},"content":[{"type":"text","text":"看門狗 | 分佈式鎖架構設計方案 -02","attrs":{}}]}]}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章