本文作者:程勝聰 - CODING 測試域產品總監
在業內如火如荼的 DevOps 轉型過程中,自動化測試始終是熱點之一,畢竟提供快速質量反饋是達成 DevOps 目標的關鍵。於是,作為測試領域的“皇冠”,自動化測試的落地實施始終為人們所關注。但是落地當中產生了種種問題甚至是爭論,經久不衰,無形中給自動化測試體系建設蒙上了層層迷霧,讓人疑惑。下面我們就一些踩過的“坑”進行探討,期望這些經驗分享能夠有助於揭開迷霧、看清方向。
要不要做自動化測試?
在軟體工程理論當中,測試相對開發來說是個“輔助”的角色,而軟體交付的產物也不必包括測試的產出。隨著軟體研發過程的複雜度提升,雖然還是輔助角色,而且其價值仍然難以單獨衡量,但是測試對質量保障的作用已經深入人心,以至於從 10 多年前開始,已經沒有人會挑戰測試作為研發體系中基本角色的存在了。
然而類似的,作為“輔助的輔助”,測試領域中的自動化測試又開始面臨著同樣的質疑:自動化測試到底有沒有價值?這個問題在 10 多年前開始被頻繁提出,而且難以回答。作為處於輔助地位的投入,人們肯定會密切關注其成本大小以及跟收益的對比。自動化測試最開始出現,是為了替代重複性的手工操作,從而節約迴歸測試的人力成本,於是要獲取正向收益的前提是執行的次數夠多:
自動化收益 = 迭代次數 x(手工執行成本 – 用例維護成本)- 用例編寫成本
所以,在 DevOps 時代的頻繁釋出測試場景下,自動化測試的價值得到了充分展現。要不要做自動化測試的問題如今已經不會造成困擾,因為當下業內已經形成了一致的認識:自動化測試是持續測試的基礎,是 DevOps 時代中不可或缺的實踐。此外,由於自動化測試的執行效率很高,體現出來的時間成本優勢更是明顯,甚至比成本優勢更能戳中這個快速釋出時代的痛點:因為不這樣做的話,我們會越來越難以應對短週期釋出所需要的快速有效驗證。
有策略地開展自動化測試
測試體系建設需要分層策略指引、介面測試往往最優先
羅馬不是一天建成的,為了達成自動化的目標,進行自動化測試體系的建設是需要投入資源和人力的。因而在具體落地過程中,我們需要充分考慮 ROI,來設計符合實際情況的目標達成路徑。自動化測試確實有很大價值,但不代表我們應該無節制地投入到各種型別的自動化測試當中:自動化測試是為了驗證既定邏輯是否符合預期,在需求變更頻繁的場景下,自動化程式碼的維護成本不可小覷。所以我們需要合適的策略,來指引自動化測試的實施——金字塔模型。
不少人對金字塔模型的第一印象,是其給出在 3 種測試上的投入佔比建議:單元測試最多、介面測試居中、UI 測試最少,比如 70%、20%、10%。但更為重要的是,Mike Cohn 提出了對測試進行分層的理念,以及給出了每個層級的測試優缺點:越接近使用者使用介面的高層次測試,粒度越粗,效率越低,寫的測試應該越少;反之越接近底層程式碼的測試,粒度越細,效率越高,應該寫的更多,也應該執行得更頻繁。
而在實踐當中,每個企業面臨的場景不同,投入情況也不一樣。比如現實情況可能並不是金字塔而是紡錘形狀的,中間的介面測試佔比最高。種種實踐表明:在自動化測試建設的初期,介面測試往往是團隊開展自動化測試的首選。這是因為介面測試兼備執行效率和體現業務價值兩方面的優點,在這個領域進行資源投入較為容易被技術團隊和業務團隊共同接受。而且由於介面定義的穩定性也較高,其維護成本也是可控的。所以相對單元測試和 UI 測試來說,介面測試的投入產出比可以說是最高的。
介面測試以介面定義管理為基礎,契約變更的同步提醒和 Mock 是關鍵
介面測試通過呼叫介面來達成測試驗證的目標,既包括系統與系統之間的介面,又包括同一系統內部各個子模組之間的介面。介面測試的重點是檢查系統/模組之間的邏輯依賴關係,以及進行互動的資料傳遞的準確性。介面測試是黑盒測試的一種,卻是最接近白盒測試的黑盒測試,故而在較早發現缺陷和執行效率上也接近於單元測試,往往被稱為“灰盒測試”。
介面測試的用例一般包括單介面用例和基於業務場景把不同介面整合到一起的多介面用例。單介面用例是基礎,而且也是開發除錯過程所需。業內比較流行的是用 Swagger 進行介面文件管理,Swagger 預定義了主流程式語言相關的程式碼註解,可以在介面實現程式碼變動之後獲取介面文件的更新,自動反映介面變更的功能對自動化用例的維護來說非常重要。
多介面用例則是測試人員根據對需求功能的理解所設計出來的,這部分用例就充分展示了自動化測試的業務價值。由於這部分用例相對複雜,團隊會需要為之準備基礎框架,甚至打造腳手架來提高編寫效率。在實現上一般會包括介面規範定義、介面間呼叫的程式碼管理、測試資料的儲存管理、執行排程平臺、結構化統計報告這幾個部分的能力。於是在業內也出現了不少在這個領域的效率工具,比如 Postman、ReadyAPI!、Robot Framework,以及“低程式碼”的平臺 apifox、Eolinker 等。
有了介面的契約定義,就可以對未上線的介面實現 Mock(測試替身或者擋板),這樣就可以不用依賴於具體的開發實現而構建場景測試用例,有利於測試開發之間或者不同開發者之間的並行協作。現在搭建 Mock 解決已經有很成熟的框架,比如 Mockito、EasyMock 等,或者平臺型工具 postman、apifox 都能夠很方便的搭建 Mock Server。
總的來說,有了可以遵循的介面定義規範、加上介面變更的資訊同步、以及提供對介面的 Mock 服務,在團隊中就可以基於 API-first 的方式實現並行開發、除錯和測試了。
高效編寫自動化用例有沒有“捷徑”可走?
現今系統的功能越來越強大,也越來越複雜,寫好自動化測試用例不是簡單的事情,這一塊也是不可忽視的投入。於是在自動化建設的實踐中,我們自然而然會追求更高效的方法。追求高效之路沒有問題,只是如何才能避免走歪呢?下面我們對一些常見的“提效工具”進行探討,期望可以對現實的實踐起到借鑑作用。
編碼方式向左,低程式碼平臺向右?
現今系統的介面往往錯綜複雜,要做好介面測試,既需要對業務層面的系統/模組之間的邏輯關係有深刻理解,又需要掌握好技術層面的各種測試框架和相關編碼技術。可以說,介面測試自動化的建設仍然面臨著較大挑戰,人們自然會尋求高效的方式進行實施,相應的就出現了對自動化用例低程式碼平臺的需求。於是出於“對測試同學技術能力的現實考慮”,研發 Leader 往往會“以負責任的態度”,尋求“入坑”去追求低程式碼平臺。低程式碼的概念在近兩年如此之火,更是導致這個話題反覆被提起。那麼我們在寫自動化測試用例的時候,到底應該是遵循老老實實寫程式碼的方式,還是應該大膽採用低程式碼平臺去快速提升覆蓋率呢?
低程式碼平臺的優勢是什麼——那就是通過降低自動化編寫的技術門檻,讓編碼能力較弱的人員也能參與進來,從而較快地從 0 開始提升自動化覆蓋率。一般低程式碼平臺都是基於介面測試自動化的資料和程式碼分離的原則而進行設計的:先把常用的介面操作方法抽象出來,並且封裝好(在 Robot Framework 中稱為關鍵字),然後通過在介面上拖拽元件組合成一個邏輯流程,再加上資料傳遞驅動形成一個完整的業務場景。所以使用低程式碼平臺給人的體驗,就是通過表格表單的操作實現自動化用例,感覺上“不需要程式設計能力”。
然而,在實踐中使用低程式碼平臺進行復雜業務測試時,對資料字典、操作的抽象(關鍵字)、設計能力的要求還是很高的,不然碰到相對底層邏輯的變更,那就需要改動茫茫多的用例。從不少團隊的實踐來看,低程式碼平臺在中長期維護上面對多變業務場景會顯得難以為繼:缺乏技術抽象思維的、非工程背景的業務(或者業務測試)成員很難持續“重構”現有的用例。而為了保證用例的準確性和整體執行效率,對自動化用例的重構是不可避免的。面對著一系列的“表格式”文件,工程師普遍認為這種方式是脆弱和低效的,從而不願意接手。
所以,是否使用低程式碼平臺取決於我們對業務發展的預期:對於非核心業務,如果預期模組是相對固化而不需要演進的,那麼不妨大膽利用低程式碼平臺開展快速覆蓋的自動化測試,低程式碼平臺確實緩解了團隊管理者的“自動化建設焦慮”,幫助團隊邁出自動化測試的第一步;而對於核心業務、註定要跟隨著業務發展而進行技術演進的模組,那麼就需要充分考慮中長期達到提升自動化編寫效率的目的。原則上我們應當承認,自動化測試用例的編寫,事實上存在著“工程門檻”,也確實是需要“工程門檻”的。哪怕使用低程式碼平臺,也需要擁有“工程師思維”,考慮“關鍵字”和資料結構的封裝和維護。總的來說,應該通過增加視覺化、減少重複性工作的方式,讓工程師更加高效地編寫自動化用例;而不是“無限制的降低門檻”,以期望未經訓練就可以寫出健壯的、高質量的自動化測試用例。
流量錄製回放方式可以取代手工寫自動化用例嗎?
關於介面測試,現實中還存在一種情況:那就是對現有系統缺失的介面測試進行“補鍋”,需要對已存在的眾多介面場景進行測試用例的補充覆蓋。如果採用傳統的測試方式,是從介面定義開始,手動梳理系統介面文件、對照著錄入測試用例、寫好邏輯斷言,然後還要在深入理解介面後,構造相應的測試資料,這是常規的方式,也是正確的方式,但是毫無疑問是個慢工細活。當我們需要在較短時間內完成一輪補充的話,這個方式顯然做不到。於是一種新的解決方案出現:自動採集真實的流量並形成介面測試用例。
流量“錄製”指的是對線上的流量請求和返回進行攔截錄製,然後記錄下來形成測試用例;而“回放”指的是把線上錄製下來的請求和返回,複製到一個準生產環境服務中,測試新功能和服務是否滿足要求。真實(資料)和高效,是流量錄製回放方式的兩大優點,而其主要的應用場景包括:
- 出現線上故障時,錄製的真實流量可以回放到開發/測試環境來進行除錯分析,這是用到“真實”的場景,也是流量錄製回放功能的基礎價值;
- 對錄製好的真實流量進行復制放大、應用到預釋出環境中作為壓測用例,這也是用到“真實”的場景,真實流量對壓測來說確實是個很好的補充;
- 如上文提及,批量形成第一次的迴歸自動化用例集合,這是用到“高效”的場景。
實現流量引流錄製的主流方式包括:Nginx 的映象複製、GoReplay 直接監聽網路介面捕獲 Http 流量、基於日誌解析,以及針對應用業務自研複製引流功能。業內最為流行的工具當屬 GoReplay 和 TCPCopy,其中 GoReplay 尤其簡單易用,而且無程式碼入侵,對線上應用的影響可以忽略不計。
那麼是不是有了流量錄製回放的功能,就不再需要手工寫自動化用例了呢?肯定不是的,我們還需要踏踏實實寫自動化用例。首先,流量錄製回放是後置的“補鍋行為”,沒有人希望等到介面上線之後才去測試,所以往往是一次性的工作;其次,流量錄製也是需要較長時間才能達到較高的用例覆蓋,對於非常用的邊界,但是重要的場景我們仍然需要人工去設計;再次,對於構建複雜的多介面組合的場景用例來說,流量錄製的方式還難以做到:對錄製下來的流量進行二次加工,可能還不如動一下腦筋去人工實現。
UI 測試用例的錄製方式靠不靠譜?
UI 測試的方式非常直觀,也很容易看到業務價值,但是 UI 介面的快速迭代特徵導致測試用例的有效生命週期很短,維護成本極高。所以,相比起單元測試和介面測試而言,脆弱、複雜、以及投入產出比低下的 UI 自動化測試,不應該成為我們的主要投入領域,一般用於覆蓋關鍵並且 UI 處於穩定狀況的業務。
UI 自動化用例還可以通過錄制方式來快速生成,現今比較流行的提供錄製方式的自動化測試工具包括:面向桌面程式的 QTP、Ranorex、面向 Web 頁面的 Selenium IDE、Chrome DevTools-Recorder、阿里的 UI Recorder、面向移動端程式的星海“鯨鴻”、WeTest UITrace 等。
現在業記憶體在一個有趣的現象,那就是儘管錄製得到的 UI 自動化用例基本上不具備可維護性,但是不少人還是會希望採用錄製方式實現自動化。究其根本,是大家對 UI 測試的期望值跟一般自動化測試不一樣:承認 UI 介面的易變性導致自動化用例維護成本很高的事實,從而乾脆當作“短暫”的、“用完即棄“的迴歸測試用例——只要錄製足夠簡單快速,大不了過一段時間(下一版本)就廢棄重新錄製。
出於“物盡其用”的觀點,做這樣的選擇看上去無可厚非,但是也要關注團隊成員對這種方式的接受度。畢竟反反覆覆去做一些註定了要放棄、從頭再來的事情,對人的士氣打擊也是顯而易見的。從長遠來看,我們還是更希望從相對穩定的層級,比如元件級別去覆蓋 UI 的測試,也就是說前移到單元測試環節。畢竟現在前端框架已經很成熟,完全可以基於框架對 UI 進行細粒度的單元測試。而對於端到端的 UI 測試,還是應該謹慎投入。
“聰明”的執行自動化用例
以下自動化測試代指的是整合級別測試,不包括單元測試
寫好用例程式碼是自動化測試重要的第一步,但是隻有執行了才能讓其價值得到展示。如同自動化收益公式所表述的那樣,自動化測試用例執行次數越多越好。而在實踐當中,當我們不能每次執行全量回歸用例集時,就需要考慮測試範圍和效率之間的平衡,有策略地執行自動化用例。下面兩點是關於自動化價值的核心原則,需要在自動化測試體系建設中牢記:
原則 1:自動化測試價值的根源在於業務,應該基於業務驅動自動化測試,始終關注優先順序最高的需求所對應的測試。
原則 2:自動化測試價值的放大器是測試執行頻率,只有跑的次數多了,才能夠賺回成本;而只有單輪次執行的夠快,才能保障較高的執行頻率。
CI/CD 中跑自動化測試會遇到什麼“坑”?
從瀑布模式時代開始,傳統的自動化測試執行方式是 nightly-run:設定任務在每天下班後跑全量回歸用例,然後第二天拿到結果後再去處理執行失敗的 case。而之所以做不到實時跑自動化測試,就是因為自動化用例累積到一定數量之後,需要好幾個小時才能執行完一輪。然而在 DevOps 時代並不會有如此“寬裕”的時間留給迴歸測試,會期望把自動化測試嵌入到 CI/CD 流水線之中,提供及時的質量反饋,例如下面幾種情況:
- 自動化測試嵌入 CI 中,也就是每次把新特性的程式碼合併(Merge Request)到主幹分支之後,除了需要跑一次全量單元測試外,往往會跑一次冒煙測試用例集;
- 自動化測試嵌入 CD 中,在每次執行生產環境部署動作之前,確認在預釋出環境上跑一次全量回歸測試用例集;部署完成後還會在生產環節跑一次冒煙測試用例集;
- 在團隊能夠實現迭代內及時編寫好自動化用例的情況下,CI 過程中還需要執行已完成特性所對應的用例,確保程式碼變更之間的相互相容;
- 甚至在迭代內,團隊中的成員可以自由建立 CI 來執行任意一個選定的自動化用例集。
一般來說,CI/CD 對執行時間是非常敏感的,所以在流水線中的整合測試部分不能耗時過長。如果不加控制地擴大測試用例集範圍整合進來,那麼執行時長必然不受控制。
設想一下,隨著程式碼寫的越來越多,每次跑自動化用例所花費的時間越來越長,最後只能降低執行自動化測試的頻率……這就比較無語了,明明已經寫好了這麼多程式碼,卻發揮不了價值,那我們還需要努力寫更多自動化用例嗎?這樣的“坑”相信很多人都碰到過。自動化測試原本功能很強大,但現在被束縛在籠子裡面,這樣的困境該如何破解呢?既然每次按部就班執行大量測試用例的方式不可取,那就需要找到更優的方法,來實現“更聰明”地執行自動化用例。
要執行得更快,第一個想到的解決方案自然是並行執行,把用例分發到分散式的機器環境中。而要做到這點,則需要設定規則保障用例之間的獨立,並且做好測試資料的管理,讓每次用例執行改動後的資料都能復原。只是這樣做會讓複雜度大大增加,而且問題出現後也難以排查。除此之外還有一個很重要的問題:每次跑的都是全量,中間夾雜著大量無效執行的用例,那麼因此得到的籠統的通過率又能夠說明什麼問題呢?除非設定簡單的標準,就是要全部通過,但是現實中因為網路抖動導致的延時、複雜的資料更新等等原因,都會導致零零星星的失敗。所以,並行執行還不能完全解決問題,我們還需要另闢蹊徑:如果跑自動化測試用例時,可以根據變更來確定用例範圍,而不是每次執行都覆蓋全量用例,是不是就可以控制測試執行的時間呢?答案就是精準測試——通過建立變更和測試的對應關係,從而更有效的驗證變更,減少不必要的全域性迴歸。精準測試從實現上大致上包括兩種:基於程式碼變更確定用例集,和基於需求變更確定用例集。
基於程式碼變更來自動確定用例集,是將測試用例與程式程式碼之間的邏輯對映關係建立起來,反映的是程式碼的測試覆蓋率。這個目標非常巨集偉,只是目前業內的方案還不成熟,還需要搭配大量的人工檢查核對。而基於需求變更來確定用例集,則是建立測試用例與業務需求之間的對映關係,反映的是需求的測試覆蓋率。這個方法在技術上聽起來不是那麼高大上,只是通過管理上的約束來實現,但是已經能很好達到我們的目的。CODING 就是基於這樣的邏輯打造了自動化用例庫,目標是讓 1)和 2)的實踐得到順暢落地,乃至實現 3)和 4)的自由執行某個自動化用例集。
CODING 助力實現“業務驅動自動化測試”
CODING 測試產品推崇的是“業務驅動測試”:一切從業務需求出發,通過建立自動化跟業務需求的關聯,可以讓自動化有的放矢的擴大覆蓋率,而不是憑空隨意去寫測試程式碼,產生重複的無效測試。
同時,CODING 自動化用例庫通過對自動化測試程式碼進行解析,把得到的函式列表匹配到相應的功能用例,基於已有功能用例與需求的關係,形成自動化用例與需求功能的對映關係:
基於這樣的關聯關係,我們就可以順藤摸瓜,根據某個自動化函式執行失敗,找到對應哪個需求有問題,然後根據需求的優先順序或者影響範圍決定是否繼續下一步,是打回修復問題還是決定繼續釋出上線。
首先,建立自動化程式碼和功能用例的匹配關係,自動化覆蓋率“一目瞭然”。
CODING 提供示例程式碼匯入,幫助使用者通過在程式碼中按照規則在註解/註釋中填寫功能用例 ID,或者人工判斷,讓解析出來的自動化程式碼函式跟已有的功能用例進行匹配,逐步提升功能用例的自動化覆蓋率(也為精準測試打下基礎):
然後,在迭代進行當中,能夠選擇性執行特定的自動化用例集。
在普通測試計劃中,可以圈選部分功能用例來執行對應的自動化用例;而在迭代測試計劃中,圈選特定需求後,對應的功能用例列表也就產生了,從而實現業務驅動自動化用例的執行。CODING 會持續加強靈活執行自動化測試方面的能力,提供更加順暢的“聰明”執行的場景方案。
總結
自動化測試體系的建設是個系統工程,涉及到人、技術、流程等方方面面,我們尤其需要全域性思維、權衡利弊,“沒有最好的、只有最合適的方式”。
首先,應該在團隊層面對齊自動化測試的目標。因為質量保障是有成本的,不能無限制的投入,沒有清晰的質量預期就無從著手去制定實施路徑。不論是團隊成員的能力建設,還是自動化測試技術框架選型,整體工作流程的設計都必須以此為基準。
其次,技術框架/工具的選型應該要適配現有或者目標工作流程。對於希望打造研發一體化工作模式的中大型團隊,在工具選用和流程制定上就需要體現跨職能的協作性,而且要向業務對齊,並且要充分考慮團隊的持續效率。比如讓自動化測試成為 DevOps 持續整合/持續部署的一環,自動化測試要體現業務價值(保障對業務需求的覆蓋),然後關注測試腳手架的可維護性,形成對自動化用例/測試資料的重構,測試執行效率及時調優的制度規範。
最後是對人的發展,業務的變化帶來技術和流程變革的要求,最終的落腳點還是要依靠人的成長。我們在制定目標、設計流程、選用工具的時候,會考慮到團隊的適配度。團隊成員也會隨之產生職責變化甚至技能升級要求,需要關注個人體驗,並對其進行引導和培養,實現“有溫度”的轉型。曾經風風火火的“去測試化”風潮的回落,正說明了測試的角色不會消失,測試開發最終還是測試,只是讓測試角色“迴歸”到工程師的本源。