驚喜來襲—歡迎“網絡設備開放社區”加入數通大家庭!
1221
2022-05-30
一、“開發者測試”? 就是“開發者來測試”
開發者測試是現代軟件工程中非常重要的一環,敏捷開發、主干開發這些先進的項目管理方法和流程都基于完善的開發者測試。當每個月甚至每周都要交付一個版本時,不可能投入大量的測試工程師來進行大規模的系統級別測試,這時候就需要把整個測試金字塔中的絕大部分測試通過自動化來完成。
我們今天談開發者測試,什么是“開發者測試”?我司有清晰的開發與測試之分。寫代碼歸開發攻城獅,測試歸測試攻城獅,大部分情況下雙方處于“紅藍對峙”狀態。這與我 10 多年前的研發團隊狀況非常相似。而現在的軟件工程,專職的“測試攻城獅”非常少,很多公司開發測試比例大于 10:1,甚至一些部門沒有測試攻城獅一說。 而測試攻城獅的角色不再是手動跑測試用例的“苦力”,而是管理產品的測試系統,對產品測試進行規劃、分析;歸納功能測試的思維導圖、設計測試用例及帶領研發團隊進行測試工作,更像一位“測試專家/測試教練”。舉個簡單的例子,我之前做的產品是在線視頻會議協作產品,我們每天的線上例會就是用自己做的產品,而且會使用自己開發的新功能測試站點來開“站會”。除了花少量的時間做 dialy update,然后就是測試專家帶領團隊(包括 PO、Architect、SM、Dev)按照計劃來進行集中(半個小時)的測試。也就是說不止通常說的 UT、API、IT 等測試,包括系統級別的測試開發也會去做,所以說“開發者測試”就是“開發者來測試”,而我們傳統的眾多測試工程師面臨三種出路:成長、轉型、淘汰。而“測試專家”在項目中的話語權也很高,之前的公司使用主干開發,有個“一進一出”的評審,團隊的這種類型的“測試專家”有一票否決權。甚至在公司有 Principle Engineer 級別的測試專家(相當于我司 20-21 級的技術專家)。
一進:對于一個功能是否能夠進入 release branch,在 release branch 打開 feature toggle 進行發布級別的測試。
一出:在 engineerrelease 時,該功能質量合格,允許 feature toggle 進入產線。
二、沒有什么測試不可以“自動化測試”
回到測試金字塔,從測試的"開發成本"、“執行成本”、“測試覆蓋率”、“問題定位”四個維度來看,基于代碼級別的白盒測試是及其重要的。
開發成本: 實現測試用例的成本。
執行成本:運行一次測試用例的成本。
測試覆蓋率:我們通常所說的 line coverage 和 branch coverage
問題定位:測試出現問題,定位問題的效率
通過測試金字塔及其四個測試維度評估,我們可以得出:
盡可能地多做 LowLevel Test :因為他們的執行速度相較于上層的幾個測試類型來說快很多且相對穩定,可以一天多次執行。一般來說,LLT 灰做到持續集成構建任務中去,甚至在 MR 中執行,保障進入代碼倉庫的代碼質量。
在自動化保障的情況下,執行一定規模的 IT、ST、UI Test:因為他們的執行速度較慢,環境依賴較多,測試相對不穩定。通常在一天執行一兩次(通常在夜里),階段性的檢查代碼質量,反饋代碼問題。
盡可能地少做大規模的手動測試:因為他們的執行速度相較 LLT 且不夠穩定,人力成本較高,也無法做到一天多次執行,每次執行都要等很久才能獲得反饋結果。但是,他們更貼近真實用戶場景,所以要確保一定周期內或者關鍵節點時間進行這種測試以確保軟件質量。
現在很多公司已經迭代發布的周期越來越短,甚至做到了 2 周。手動測試顯然無法適應這種開發模式,而把手動測試的測試用例通過各種技術方案自動化是唯一途徑。代碼層面,從底層業務代碼到 UI 代碼,只要架構設計合理,都是可以做 UT。最頂層的 UI 交互測試,測試用例也是可以自動化運行(大部分 UI 框架都可以通過 accessibility 的接口進行 UI 自動化測試),我看到華為手機硬件部門都可以自動化測試“摔手機”這種極端測試,軟件有啥做不到?至少有些業界技術大牛公司的某些產品,從代碼提交 Merge Request,到產品上產線是可以以天來計算的。這種產品的測試是不會也不可能通過手工測試來完成的。
三、開發者測試“利在當下”,“贏得未來”
很多人都認為底層的開發者測試,花了大量的時間,寫了大量的代碼,然后來保證功能的正確性,但是每次代碼功能或者結構的的變更都要修改測試代碼。而我手動調試和驗證效率更高,甚至一些開發者測試更多的是為了指標。實際上通過 UT,API 測試來調試代碼與自己手動運行調試區別不大,但是通過開發者測試對代碼進行調試,從而保證當前項目迭代的質量;但是其更重要的作用不是這個。通常在我們 bug 分類中有這樣一些名詞 : Build Regression Bug, Release RegressionBug。
BuildRegression Bug : 開發中同樣的功能在新版本出現一個 bug,但是在之前的版本沒有這個問題,我們叫做 Build Regression Bug.
ReleaseRegression Bug : 產線上同樣的功能在新版本出現一個 bug,但是在之前的版本沒有這個問題,我們叫做 Release Regression Bug.
我們每次提交到產品中的代碼,沒有人可以保證其 100%不會出現問題,在敏捷開發的這種快速迭代下,不太可能進行全功能的手動測試,所以開發者測試,特別是底層的 UT、API 測試、集成測試,能夠很容易的識別發現這類問題,也就是開發者測試一個重要的功能是為了防御后面改動的代碼對現在代碼的影響。所以說開發者測試是”利在當下“,”贏得未來“。
四、灰度 TDD,不強求必須先寫測試代碼
對于 TDD,大家的認知是先寫測試代碼,再在寫實現代碼,這個說法對也不對。概念上沒錯,但是如果嚴格這樣做,效率未必最高,這也是 TDD 很難推廣的原因之一。我們把編碼實現分成 3 個部分:實現代碼、測試代碼、調試代碼。按照 TDD 的概念時先寫測試代碼、然后編碼,最后調試。我們通常在代碼實現時,一開始不大可能考慮的非常清晰,把接口定義的完全準確,如果嚴格按照測試、編碼、調試來做,測試代碼要隨著編碼頻繁修改。當然這本身不是什么大問題,在實際執行過程中,很多人習慣先搭好代碼框架、測試框架,然后再編碼,測試。等測試完成后再進行調試。所以從華為灰度管理的角度上來說,只要單元測試在調試之前,都可以稱作 TDD 開發模式。BTW,當然現在開始流行 BDD,這里想說的是如果連我說的 TDD 都做不到的團隊,就不要考慮 BDD 了。
(Behavior-Driven Development:BDD 將 TDD 的一般技術和原理與領域驅動設計(DDD)的想法相結合。 BDD 是一個設計活動,您可以根據預期行為逐步構建功能塊。 BDD 的重點是軟件開發過程中使用的語言和交互。行為驅動的開發人員使用他們的母語與領域驅動設計的語言相結合來描述他們的代碼的目的和好處。 使用 BDD 的團隊應該能夠以用戶故事的形式提供大量的“功能文檔”,并增加可執行場景或示例。 BDD 通常有助于領域專家理解實現而不是暴露代碼級別測試。它通常以 GWT 格式定義:GIVEN WHEN&THEN。)
五、UT 覆蓋率 100%真的很不好
于單元測試,我們都會關注一個指標“覆蓋率”。不管模塊、函數、行、分支覆蓋率,必須要有一定比例的覆蓋率。但是每一項你都做到了 100%,那么會給你打“差評”。不是說你做到不好(這里不談是不是用了正確的方式),而是成本和性價比問題。以最難達到的分支覆蓋率(branch coverage)為例,如果要做到 100%的覆蓋率,有些內存分配或者容錯保護的分支都必須測試到,那么你的測試用例考慮要翻倍,但是并沒有帶來的相應價值。甚至一些代碼條件分支在程序運行的生命周期內都沒有被執行過。
模塊覆蓋率:業務模塊代碼通過 UT,架構模塊代碼通過 IT;就從 UT 的覆蓋率的角度上去看,不需要去測試架構代碼。
函數覆蓋率:不要專門為一些無任何邏輯的代碼去寫 UT。比如我們有些函數就是 get/set 一個屬性,內部實現就用一個變量來賦值保存。這種函數寫 UT 就是為了覆蓋率而寫,沒有任何真正的意義。
行覆蓋率:通常來看平局 80%上下的行覆蓋率是一個合理的指標,有些可以為 0%,而有些需要 100%,如果全部代碼都超過 90%,其成本較高,效率較低,不建議這樣做。
分支覆蓋率:越復雜的業務邏輯,越要寫更多的測試用例來覆蓋,而一些內存分配出錯邏輯判斷可以不需要測試。
六、用測試來驅動架構和代碼質量
這里談測試驅動架構和代碼質量,主要說的是讓代碼具備完善的可測試性,什么是代碼的可測試性?簡單的說就是類與類之間,模塊與模塊關系解耦,類與類,模塊與模塊通過接口編程。依賴的接口通過被動注入式傳入,而不是主動獲取式。對于程序正常運行時,所傳入的接口參數是真實的業務對象,而做測試時,可以傳入 fake 的模擬實現。當然不是所有的依賴模塊都這樣做,一些與業務無關的 UtilityLibrary,或者一些特定的數據對象實現,可以直接調用。
這里我們講到了 fake 與 mock,關于 TestDoubles,基本上的概念如下,具體每種代表什么意義,大家可以自行上網搜索
虛擬對象(dummy)
存根(stub)
間諜(spy)
模擬對象(mock)
偽對象(fake)
當前我司大家在做開發者測試時,基本上都在用 Mock Object(實際上在用的過程中,很多是在用入參返回值控制的 Stub)。還記得前些年 DHH(David HeinemeierHansson)的那篇文章《TDD is dead, Long live testing》,其中一點對于 TDD 中過度的使用 Mock/Stub 導致架構上諸多問題。而 TDD 創始人 Kent Beck 則說他從來不用 Mock。?雖然通過 Mock 的方式也是可以測試代碼,但是實際上不得不用 Mock 基本上意味著我們的代碼關聯性較強,模塊顯示依賴較重,模塊移植性較差,特別是 C 語言編程這種問題特別多。以至于現在很多模塊根本無法開展單元測試,更多的是在做集成測試。
為什么會出現這種情況? 我們的高級別的架構師更多的在考慮系統級別的架構設計,把系統模塊,各個應用之間的關系梳理的非常清晰,通常情況下,高級別的架構師可以把系統模塊或應用之間的關系設計的較為合理。然而到了具體的應用業務內部的設計與實現,交給了低級別的架構師來完成。實際上這些模塊內部的代碼量并不小,很多都是幾十萬行甚至上百萬行的代碼量。這時候架構師的水平決定了代碼的 Clean Code 質量。我司目前代碼上的問題很多不是系統架構的問題,而是具體業務實現中,缺少嚴格的要求和合理的架構設計。如果在應用級別有一套架構方案來規范,那么至少在模塊的接口以及模塊與模塊之前的交互上也能達到和系統設計一樣較為清晰合理。那么不確定的部分就時每個子模塊內部幾千上萬行的代碼部分。
最近做項目技術評審時,遇到一個典型的例子。一個團隊寫了一個 socket 庫,底層依賴特定平臺的系統庫。如果要把該 socket 庫移植到 linux 或者別的平臺,就需要較大規模的重構。重構的方法應該很容易理解,就是通過適配器模式,把底層的操作抽象成接口(這里針對 socket 庫來說,底層庫是一種依賴,不同的場景要區別對待底層庫,不要一概而論),實際代碼不關注具體平臺的具體實現,而通過實現不同平臺的 adapter 來進行適配解決該問題。然而在開發初期,其設計在搭建代碼框架、測試框架時就會發現底層庫是一種耦合,測試時不得不做 Mock 來實現測試替身。如果這時候就考慮解耦式設計,那么當支持不同平臺時,架構本身就天然支持而不需要再重構了。
之所以提出用測試驅動架構和代碼質量,當給測試提出一個很高的標準時(之前做過的項目中有的項目明確 UT 不允許使用 Mock,測試框架甚至只有單一的 Catch2),大家不得不從架構上去解決測試的問題,當測試的問題解決時,代碼架構上的 CleanCode L3 自然而然就達到了。
七、從“我要寫測試依賴代碼”到“我要寫測試依賴代碼”
這句話看著很奇怪,實際上是從根本上去解決底層開發者測試的根本方法。?模塊之間有依賴,不管是通過 Mock 還是 Fake 的方法,不管架構上如何合理,這種依賴是不能消除的,我們做到更多的是合理的設計讓依賴與模塊解耦。第一個“我要寫測試依賴代碼”,指的是當我實現我的模塊時,我要寫測試代碼來測試。然而我要考的是如何寫我的測試依賴。這時候常常出現了的問題,比如是 A1, A2, A3 三個模塊依賴與 B1,B2 兩個模塊,通常情況下我司的做 A1,A2,A3 的團隊或者個人會自己去寫 B1,B2 的依賴,導致重復的測試代碼,如果模塊設計不合理,測試依賴太多,單元測試成本太高。而第二個“我要寫測試依賴代碼”指的是,當我實現我的代碼時,我要考慮的是依賴我的模塊在測試時,對于我的依賴該怎么解決,"我要寫測試依賴代碼”(就是我說的 fake 對象與實現)來幫助依賴我的模塊解決測試依賴問題。同樣的情況在測試 A1,A2,A3 的時候,B1,B2 的依賴已經存在,只要直接關注在測試用例本身就可以了。具體來說:
思維轉變、測試驅動:開發一個模塊,不要先考慮怎么測試自己,先考慮如果別人依賴我,我該怎么讓別人更容易測試。模塊的提供者,不止要提供模塊代碼,還要提供一個可復用的 Faked 對象(調用驗證;返回值;參數驗證;參數處理;功能模擬等)。
模塊代碼的編寫者實現自己的 Fake 實現,基本上大部分的代碼是由模塊編寫者來完成,同時這是一個可復用的 Fake 實現。模塊依賴方根據自己一些特殊的業務需求來添加自己的代碼。基本上遵循 80/20 原則。
架構上依賴解耦,通過注入依賴的方式進行接口編程。產品運行時,模塊使用真實的實現對象,而開發者測試使用 Fake 對象。
當編寫測試代碼時,所有依賴的接口、依賴的實現都基本完成,更多的關注些測試用例而不是測試依賴。
開發者
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。