開發者測試:你必須知道 7 件事

duanhao發表於2021-09-09

摘要:寫程式碼歸開發攻城獅,測試歸測試攻城獅,大部分情況下雙方處於“紅藍對峙”狀態。

一、“開發者測試” 就是“開發者來測試”

開發者測試是現代軟體工程中非常重要的一環,敏捷開發、主幹開發這些先進的專案管理方法和流程都基於完善的開發者測試。當每個月甚至每週都要交付一個版本時,不可能投入大量的測試工程師來進行大規模的系統級別測試,這時候就需要把整個測試金字塔中的絕大部分測試透過自動化來完成。

圖片描述

我們今天談開發者測試,什麼是“開發者測試”? 我司有清晰的開發與測試之分。寫程式碼歸開發攻城獅,測試歸測試攻城獅,大部分情況下雙方處於“紅藍對峙”狀態。這與我 10 多年前的研發團隊狀況非常相似。而現在的軟體工程,專職的“測試攻城獅”非常少,很多公司開發測試比例大於 10:1,甚至一些部門沒有測試攻城獅一說。 而測試攻城獅的角色不再是手動跑測試用例的“苦力”,而是管理產品的測試系統,對產品測試進行規劃、分析歸納思維導圖、設計測試用例及帶領研發團隊進行測試工作,更像一位“測試專家/測試教練”。舉個簡單的例子,我之前做的產品是線上視訊會議協作產品。我們每天的線上例會就是用自己做的產品,而且會使用自己開發的新站點來開“站會”。除了花少量的時間做 dialy update,然後就是測試專家帶領團隊(包括 PO、架構師、SM、Dev)按照計劃來進行集中(半個小時)的測試。所以說“開發者測試”就是“開發者來測試”,而我們傳統的眾多測試工程師面臨三種出路:成長、轉型、淘汰。“測試專家”在專案中的話語權也很高,我之前的公司使用主幹開發,有個“一進一出”的評審,團隊的這種型別的“測試專家”有一票否決權。甚至在公司有 PE 級別的測試專家(相當於我司 20-21 級的技術專家)。

  • 一進:對於一個功能是否能夠進入 release branch,在 release branch 開啟 feature toggle 進行釋出級別的測試。

  • 一出:在 engineer release 時,該功能質量合格,允許 feature toggle 進入產線。

二、沒有什麼測試不可以“自動化測試”

回到測試金字塔,從測試的"開發成本"、“執行成本”、“測試覆蓋率”、“問題定位”四個維度來看,基於程式碼級別的白盒測試是及其重要的。

圖片描述

  • 開發成本: 實現測試用例的成本。

  • 執行成本:執行一次測試用例的成本。

  • 測試覆蓋率:我們通常所說的 line coverage 和 branch coverage

  • 問題定位:測試出現問題,定位問題的效率

透過測試金字塔及其四個測試維度評估,我們可以得出:

  • 儘可能地多做 Low Level Test :因為他們的執行速度相較於上層的幾個測試型別來說快很多且相對穩定,可以一天多次執行。一般來說,LLT 灰做到持續整合構建任務中去,甚至在 MR 中執行,保障進入程式碼倉庫的程式碼質量。

  • 在自動化保障的情況下,執行一定規模的 IT、ST、UI Test:因為他們的執行速度較慢,環境依賴較多,測試先對不穩定。通常在夜裡執行一次,階段性的檢查程式碼質量,反饋程式碼問題。

  • 儘可能地少做大規模的手動測試:因為他們的執行速度相較 LLT 且不夠穩定,人力成本較高,也無法做到一天多次執行,每次執行都要等很久才能獲得反饋結果。但是,他們更貼近真實使用者場景,所以要確保一定週期內或者關鍵節點時間執行以下這幾個測試以確保軟體質量。

現在很多公司已經迭代釋出的週期越來越短,甚至做到了 2 周。手動測試顯然無法適應這種開發模式,而把手動測試的用例透過各種技術方案自動化是唯一途徑。程式碼層面,從底層業務程式碼到 UI 程式碼,只要架構設計合理,都是可以做 UT。最頂層的 UI 互動測試,測試用例也是可以自動化執行(大部分 UI 框架都可以透過 accessibility 的介面進行 UI 自動化測試),試想連我們們手機硬體都可以自動化測試“摔手機”這種極端測試,軟體有啥做不到?至少有些業界技術大牛公司的某些產品,從程式碼提交 Merge Request,到產品上產線是可以以天來計算的。這種產品的測試是不會也不可能透過手工測試來完成的。

三、開發者測試”利在當下“,”贏得未來“

很多人都認為底層的開發者測試,花了大量的時間,寫了大量的程式碼,然後來保證功能的正確性,但是每次程式碼功能或者結構的的變更都要修改測試程式碼。我手動除錯和驗證效率更高。的確透過 UT,API 測試來除錯程式碼與自己手動執行除錯區別不大,但是透過開發者測試對程式碼進行除錯,從而保證當前專案迭代的質量;但是其更重要的作用不是這個。

我們在 bug 分類中有這樣一些名詞 :Build Regression Bug, Release Regression Bug。

  • Build Regression Bug : 開發中同樣的功能在新版本出現一個 bug,但是在之前的版本沒有這個問題,我們叫做 Build Regression Bug.

  • Release Regression Bug : 產線上同樣的功能在新版本出現一個 bug,但是在之前的版本沒有這個問題,我們叫做 Release Regression Bug.

我們每次 commit 到產品中的程式碼,沒有人可以保證其 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,關於 Test Doubles,基本上的概念如下,具體每種代表什麼意義,大家可以自行上網搜尋

  • 虛擬物件(dummy)

  • 存根(stub)

  • 間諜(spy)

  • 模擬物件(mock)

  • 偽物件(fake)

圖片描述

當前我司大家在做開發者測試時,基本上都在用 Mock Object(實際上在用的過程中,很多是在用入參返回值控制的 Stub)。拋開概念上的問題,雖然透過 Mock 的方式也是可以測試程式碼,但是實際上用 Mock 基本上意味著我們的程式碼關聯性較強,模組顯示依賴較重,模組移植性較差,特別是 C 語言程式設計這種問題特別多。以至於現在很多模組根本無法開展單元測試,更多的是在做整合測試。

為什麼會出現這種情況? 我們的高階別的架構師更多的在考慮系統級別的架構設計,把系統模組,各個應用之間的關係梳理的非常清晰,通常情況下,高階別的架構師可以把系統模組或應用之間的關係設計的較為合理。然而到了具體的應用業務內部的設計與實現,交給了低階別的架構師來完成。實際上這些模組內部的程式碼量並不小,很多都是幾十萬行甚至上百萬行的程式碼量。這時候架構師的水平決定了程式碼的 Clean Code 質量。我司目前程式碼上的問題很多不是系統架構的問題,而是具體業務實現中,缺少嚴格的要求和合理的架構設計。如果在應用級別有一套架構方案來規範,那麼至少在模組的介面以及模組與模組之前的互動上也能達到和系統設計一樣較為清晰合理。那麼不確定的部分就時每個子模組內部幾千上萬行的程式碼部分。

之所以提出用測試驅動架構和程式碼質量,當給測試提出一個很高的標準時,大家不得不從架構上去解決測試的問題,當測試的問題解決時,Clean Code L3 自然而然就達到了。

七、從“我要寫測試依賴程式碼”到“我要寫測試依賴程式碼”

這句話看著很奇怪,實際上是從根本上去解決單元測試的根本方法。 模組之間有依賴,不管是透過 Mock 還是 Fake 的方法,不管架構上如何合理,這種依賴是不能消除的,我們做到更多的是合理的設計讓依賴與模組解耦。第一個“我要寫測試依賴程式碼”,指的是當我實現我的模組時,我要寫測試程式碼來測試。而然我要考的是如何寫我的測試依賴。而第二個“我要寫測試依賴程式碼”指的是,當我實現我的程式碼時,我要考慮的是依賴我的模組在測試時,對於我的依賴該怎麼解決,"我要寫測試依賴程式碼”(就是我說的 fake 物件與實現)來幫助依賴我的模組解決測試依賴問題。

  • 思維轉變、測試驅動:開發一個模組,不要先考慮怎麼測試自己,先考慮如果別人依賴我,我該怎麼讓別人更容易測試。模組的提供者,不止要提供模組程式碼,還要提供一個可複用的 Faked 物件(呼叫驗證;返回值;引數驗證;引數處理;功能模擬等)。

  • 模組程式碼的編寫者實現自己的 Fake 實現,基本上大部分的程式碼是由模組編寫者來完成,同時這是一個可複用的 Fake 實現。模組依賴方根據自己一些特殊的業務需求來新增自己的程式碼。基本上遵循 80/20 原則。

  • 架構上依賴解耦,透過注入依賴的方式進行介面程式設計。開發者測試使用 Fake 來實現依賴。

  • 當編寫測試程式碼時,所有依賴的介面、依賴的實現都基本完成,更多的關注些測試用例而不是測試依賴。


作者:華為雲開發者社群


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/1343/viewspace-2796736/,如需轉載,請註明出處,否則將追究法律責任。

相關文章