天天看點

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

摘要:開發者測試是現代軟體工程中非常重要的一環,靈活開發、主幹開發這些先進的項目管理方法和流程都基于完善的開發者測試。

開發者測試是現代軟體工程中非常重要的一環,靈活開發、主幹開發這些先進的項目管理方法和流程都基于完善的開發者測試。當每個月甚至每周都要傳遞一個版本時,不可能投入大量的測試工程師來進行大規模的系統級别測試,這時候就需要把整個測試金字塔中的絕大部分測試通過自動化來完成。

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

我們今天談開發者測試,什麼是“開發者測試”? 我司有清晰的開發與測試之分。寫代碼歸開發攻城獅,測試歸測試攻城獅,大部分情況下雙方處于“紅藍對峙”狀态。這與我10多年前的研發團隊狀況非常相似。而現在的軟體工程,專職的“測試攻城獅”非常少,很多公司開發測試比例大于10:1,甚至一些部門沒有測試攻城獅一說。 而測試攻城獅的角色不再是手動跑測試用例的“苦力”,而是管理産品的測試系統,對産品測試進行規劃、分析;歸納功能測試的思維導圖、設計測試用例及帶領研發團隊進行測試工作,更像一位“測試專家/測試教練”。

舉個簡單的例子,我之前做的産品是線上視訊會議協作産品,我們每天的線上例會就是用自己做的産品,而且會使用自己開發的新功能測試站點來開“站會”。除了花少量的時間做dialy update,然後就是測試專家帶領團隊(包括PO、Architect、SM、Dev)按照計劃來進行集中(半個小時)的測試。也就是說不止通常說的UT、API、IT等測試,包括系統級别的測試開發也會去做,是以說“開發者測試”就是“開發者來測試”,而我們傳統的衆多測試工程師面臨三種出路:成長、轉型、淘汰。而“測試專家”在項目中的話語權也很高,之前的公司使用主幹開發,有個“一進一出”的評審,團隊的這種類型的“測試專家”有一票否決權。甚至在公司有Principle Engineer級别的測試專家(相當于我司20-21級的技術專家)。

一進:對于一個功能是否能夠進入release branch,在release branch打開feature toggle進行釋出級别的測試。

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

回到測試金字塔,從測試的"開發成本"、“執行成本”、“測試覆寫率”、“問題定位”四個次元來看,基于代碼級别的白盒測試是及其重要的。

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

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

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

測試覆寫率:我們通常所說的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.

我們每次送出到産品中的代碼,沒有人可以保證其100%不會出現問題,在靈活開發的這種快速疊代下,不太可能進行全功能的手動測試,是以開發者測試,特别是底層的UT、API測試、內建測試,能夠很容易的識别發現這類問題,也就是開發者測試一個重要的功能是為了防禦後面改動的代碼對現在代碼的影響。是以說開發者測試是”利在當下“,”赢得未來“。

對于TDD,大家的認知是先寫測試代碼,再在寫實作代碼,這個說法對也不對。概念上沒錯,但是如果嚴格這樣做,效率未必最高,這也是TDD很難推廣的原因之一。我們把編碼實作分成3個部分:實作代碼、測試代碼、調試代碼。按照TDD的概念時先寫測試代碼、然後編碼,最後調試。我們通常在代碼實作時,一開始不大可能考慮的非常清晰,把接口定義的完全準确,如果嚴格按照測試、編碼、調試來做,測試代碼要随着編碼頻繁修改。 當然這本身不是什麼大問題,在實際執行過程中,很多人習慣先搭好代碼架構、測試架構,然後再編碼,測試。等測試完成後再進行調試。是以從華為灰階管理的角度上來說,隻要單元測試在調試之前,都可以稱作TDD開發模式。BTW,當然現在開始流行BDD,這裡想說的是如果連我說的TDD都做不到的團隊,就不要考慮BDD了。

(Behavior-Driven Development:BDD将TDD的一般技術和原理與領域驅動設計(DDD)的想法相結合。 BDD是一個設計活動,您可以根據預期行為逐漸建構功能塊。 BDD的重點是軟體開發過程中使用的語言和互動。 行為驅動的開發人員使用他們的母語與領域驅動設計的語言相結合來描述他們的代碼的目的和好處。 使用BDD的團隊應該能夠以使用者故事的形式提供大量的“功能文檔”,并增加可執行場景或示例。 BDD通常有助于領域專家了解實作而不是暴露代碼級别測試。它通常以GWT格式定義:GIVEN WHEN&THEN。)

于單元測試,我們都會關注一個名額“覆寫率”。不管子產品、函數、行、分支覆寫率,必須要有一定比例的覆寫率。但是每一項你都做到了100%,那麼會給你打“差評”。不是說你做到不好(這裡不談是不是用了正确的方式),而是成本和成本效益問題。以最難達到的分支覆寫率(branch coverage)為例,如果要做到100%的覆寫率,有些記憶體配置設定或者容錯保護的分支都必須測試到,那麼你的測試用例考慮要翻倍,但是并沒有帶來的相應價值。甚至一些代碼條件分支在程式運作的生命周期内都沒有被執行過。

子產品覆寫率:業務子產品代碼通過UT,架構子產品代碼通過IT;就從UT的覆寫率的角度上去看,不需要去測試架構代碼。

函數覆寫率:不要專門為一些無任何邏輯的代碼去寫UT。比如我們有些函數就是get/set一個屬性,内部實作就用一個變量來指派儲存。這種函數寫UT就是為了覆寫率而寫,沒有任何真正的意義。

行覆寫率:通常來看平局80%上下的行覆寫率是一個合理的名額,有些可以為0%,而有些需要100%,如果全部代碼都超過90%,其成本較高,效率較低,不建議這樣做。

分支覆寫率:越複雜的業務邏輯,越要寫更多的測試用例來覆寫,而一些記憶體配置設定出錯邏輯判斷可以不需要測試。

這裡談測試驅動架構和代碼品質,主要說的是讓代碼具備完善的可測試性,什麼是代碼的可測試性?簡單的說就是類與類之間,子產品與子產品關系解耦,類與類,子產品與子產品通過接口程式設計。依賴的接口通過被動注入式傳入,而不是主動擷取式。對于程式正常運作時,所傳入的接口參數是真實的業務對象,而做測試時,可以傳入fake的模拟實作。當然不是所有的依賴子產品都這樣做,一些與業務無關的Utility Library,或者一些特定的資料對象實作,可以直接調用。

這裡我們講到了fake與mock,關于Test Doubles,基本上的概念如下,具體每種代表什麼意義,大家可以自行上網搜尋

虛拟對象(dummy)

存根(stub)

間諜(spy)

模拟對象(mock)

僞對象(fake)

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

目前我司大家在做開發者測試時,基本上都在用Mock Object(實際上在用的過程中,很多是在用入參傳回值控制的Stub)。還記得前些年DHH(David Heinemeier Hansson)的那篇文章《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),大家不得不從架構上去解決測試的問題,當測試的問題解決時,代碼架構上的Clean Code 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對象。

當編寫測試代碼時,所有依賴的接口、依賴的實作都基本完成,更多的關注些測試用例而不是測試依賴。

點選關注,第一時間了解華為雲新鮮技術~