導讀:對于軟體測驗來說,怎么樣才算測夠了?如何評價測驗的有效性?那么多測驗用例,以后怎么刪?在軟體測驗中會遇到非常多的問題,阿里研究員鄭子穎分享了18個他總結出的難題以及相關看法,希望對同學們有所啟發,
十多年前我在上一家公司的時候看到過內部有個網站有一個Hard Problems in Test的串列,上面大概有三四十個問題的樣子,是各個部門的測驗同學提供的,但可惜后來那個list失傳了,我很后悔自己當時沒有保存一份,后來很多次我都想要找到那份list,因為上面列的那些問題指出了測驗專業在自身專業性上的巨大發展空間,那份list上的問題讓當時的我相信,軟體測驗這件事情本身的難度一點都不亞于軟體開發,甚至可能更難一點,
如果今天要重建這么一份Hard Problems in Test串列,下面這些問題是我會加到這份串列上的[1],
一 測驗充分度(Test Sufficiency)
如何回答“測夠了嗎“(包括測新和測舊),代碼覆寫率是衡量測驗充分性的起點,但遠遠不是終點,要回答”測夠了嗎“,至少還要考慮是否測了所有的場景、所有的狀態、所有的狀態轉移路徑、所有的事件序列、所有可能的配置、所有可能的資料等等等等,即便如此,我們可能還是無法100%確信我們已經測夠了,可能我們最終只能做到非常趨近于測夠了[2],
二 測驗有效性(Test Effectiveness)
如何評價一組測驗用例的發現bug的能力,有效性(發現bug的能力)和充分性(測夠了沒有)是兩個正交的屬性,評價測驗用例有效性可以通過正向的分析進行,例如,分析測驗用例是否校驗了所有在測驗程序中SUT落庫的資料,更具有通用性的做法是變異測驗(Mutation Testing),即在被測代碼里注入不同的“人造bug”,統計多少能被測驗用例感知到,目前變異測驗我們已經有工程化規模化的落地了,后續的作業重點有:1)如何防止鈍化(或曰“殺蟲劑效應”),2)不但對被測代碼進行注入,還能對配置、資料等進行更全面的注入,
三 測驗用例瘦身
以前廣告行業有句話:我知道廣告費有一半是浪費掉的,但不知道哪一半是浪費掉的[3],
軟體測驗也有類似的困惑:那么多用例,要花那么多時間去跑,我知道這里面有很多時間是浪費掉的,但我不知道哪些時間是浪費掉的,浪費的形式包括:
-
冗余步驟:有些是浪費在一些重復的步驟上,每個用例都要去做一些類似的資料準備,每個用例都要去執行一些中間程序(這樣才能推進到下一步),
-
等價類:一個支付場景,我要不要在所有的國家、所有的幣種、所有的商戶、所有的支付渠道和卡組的排列組合都測一遍?這么測,代價太高,不這么測,我擔心可能某個特定商戶在某個特定國家有個特定邏輯我就漏掉了,對于具體的業務,還可以進行人肉分析,有沒有更通用的、而且比較完備和可靠的等價類分析的技術手段?
-
我有N個用例,我猜這N個用例里面可能存在M個用例,即使刪掉這M個用例,剩下的N-M個用例的效果和之前N個用例的效果一樣,如何識別是否存在這樣的M個用例、如果存在的話是哪M個,
我參加過內部一場質量線晉升到P9的評審,當時有個評委問了那位同學一個問題:“那么多測驗用例,以后你怎么刪”,這個問題看似簡單,其實非常難,我覺得,從原理上來說,如果測驗充分度和測驗有效性的度量都做的非常好了、度量成本非常低了,我們是可以通過大量的不斷的嘗試來刪用例的,這是一種工程化的思路,也許還有其他的理論推導的思路,
四 測驗分層
很多團隊都會糾結到底要不要做全鏈路回歸、做到什么程度,這個問題的核心點就是:有沒有可能、有沒有一種做法,只要把系統間的邊界約定的足夠好足夠完整,就可以做到在改動一個系統的代碼后,不需要和上下游系統進行集成測驗,只要按照邊界約定驗證好自己的代碼就可以確保沒有任何regression了,
包括我在內的很多人相信那是可能的,但既無法證明,也不敢在實操中就完全不跑集成,我們也缺乏可以完全復制的成功經驗,缺乏一套完整的方法論指導開發團隊和QA團隊要怎么做就可以達到回歸無需集成上下游,
有時候,我覺得我現在就像是哥德堡的市民,不斷的走啊走,嘗試找出一條一次性不重復的走過那7座橋的路線,但也許就有那么一天,有一個像歐拉那樣的人會出現在我面前,用理論證明告訴我,那是不可能的,
五 減少分析遺漏
分析遺漏是很多故障的原因,開發做系分的時候,有一個corner case沒考慮到、沒有處理,測驗做測分的時候,忘記考慮某個特殊場景了,兼容性評估,評估下來沒有兼容性問題的,但結果是有的,而且很多時候,分析遺漏屬于unknown unknowns,我壓根就不知道我不知道,有沒有一套方法和技術,可以減少分析遺漏,可以把unknown unknowns轉化為knowns?
六 用例自動生成
Fuzz Test、Model Based Test、錄制回放、Traffic Bifurcation(引流)等都是自動生成用例的手段,有些已經比較成熟(例如單系統的錄制回放、引流),有些多個團隊都在探索(例如Fuzz),有些則一直沒有大規模的成功實踐(例如MBT),我們也有過探索如何從PRD里通過NLP來生成用例,用例自動生成中,有時候難點還不是生成test steps,難度反而是怎么生成test oracle,Anyway,測驗用例自動生成是一個非常大的領域,這個方向上未來可以做的還非常多,
七 問題自動排查
包括線上和線下,對于比較初級的問題,自動排查方案往往有兩個局限性,首先,方案不夠通用,多多少少比較定制化,其次,比較依賴人工積累規則(說的好聽點叫“專家經驗”),主要是通過記錄和重復人肉排查的步驟來實作,然而,每個問題都不完全一樣,問題稍微一變,之前的排查步驟可能就不work了,現在有一些技術,比如呼叫鏈路的自動比對,對排查問題和缺陷自動定位很有幫助,
八 缺陷自動修復
阿里的Precfix、Facebook的SapFix等是目前比較知名的一些工業界的做法,但總的來說,現有的技術方案,都有這樣那樣的局限性和不足,這個領域還在相對早期階段,后面的路還很長,
九 測驗資料準備
測驗用例的一個重要設計原則是:測驗用例之間不應該有依賴關系,一個測驗用例的執行結果不應該受到其他測驗用例的執行結果(包括是否執行)的影響,基于這個原則,傳統的最佳時間是確保每個測驗用例都應該是自給自足的:一個用例需要觸發的后臺處理流程應該由這個用例自己來觸發,一個測驗用例需要的測驗資料應該自己來準備,等等,但如果每個用例所需要用到的測驗資料都是自己來從頭準備的,執行效率就比較低,怎么既不違背“測驗用例之間不應該有依賴關系”的大原則,又能減少測驗資料的準備時間?
我設想的是一種更加完備的資料銀行,每個測驗用例執行完后,都會把它自己產生的資料交給資料銀行,例如,一個在某個特定國家的已經通過KYC、已經綁了一張卡的會員,一筆已經支付成功的交易,一個已經完成入駐簽約流程的商戶,下一個測驗用例開始的時候,會先問一下資料銀行:“我要一個滿足這樣這樣條件的商戶,你有沒有”,上個用例跑出來的那個商戶正好符合條件,資料銀行就會把商戶“借”給這個用例用,而且一旦借出,直到被歸還前,這個商戶不會被借給其他用例,
經過一段時間的運行,資料銀行能夠學習到每個測驗用例需要什么樣的資料、以及會產生什么樣的資料,這個知識是通過學習得到的,不需要人肉去添加描述,所以也能適用于老系統的存量用例,有了這個知識,資料銀行可以實作兩個優化:
-
一次測驗執行批次開始后,資料銀行會看到這個批次中后面那些用例需要什么樣的資料,提前先準備起來,這樣,等執行到那些用例的時候,資料銀行里就已經有符合條件的資料準備好了,
-
根據每個測驗用例需要什么樣的資料、以及會產生什么樣的資料,資料銀行可以合理的編排測驗用例的執行先后次序,最大化的實作測驗資料的復用,減少測驗資料的量和準備開銷,
測驗銀行把測驗資料“借”給用例的時候,可以有多種不同的模式,可以是獨占(exclusive)的,也可以是共享的,共享的也可以指定共享讀、共享寫、還是都只讀不能寫(例如,一個商戶可以被多個用例用來測驗下單支付結算場景,但這些用例都不可以去修改這個商戶本身,例如重新簽約),
如果把開關、定時任務等resource也作為一種廣義的測驗資料由資料銀行來管理,能實作測驗用例盡可能并行執行,例如,有N個用例都需要修改一個開關值,這N個用例如果并行執行的話就會相互影響,他們相互之間應該串行執行,但N個用例中的任何一個,都可以和這N個用例之外的用例并行執行,資料銀行掌握了每個用例對各種資源的使用模式的詳細情況,再加上每個用例的平均運行時間等資料,就可以最優化、最準確的對一批測驗用例進行編排,做到可以并行的都盡可能并行、不能并行的確保不并行,而且還可以在一個批次的執行程序中不斷的調整余下還未執行的用例的編排,
這樣一個資料銀行是普遍適用的,不同業務之間的差異無非是具體的業務物件和resource不一樣,這些差異可以通過插件形式實作,如果有這么一個通用的資料銀行[4],可以很方便的adopt,大量的中小軟體團隊的測驗效率都可以得到明顯的提高,這樣的一個更加完備的資料銀行的想法,我到目前為止還只是想法,一直沒有機會實踐,
十 例外測驗
一個分布式系統,它的內部、內部各部分之間以及它和外部的互動都會出現各種例外:訪問超時、網路連接和耗時的抖動、連接斷開、DNS無法決議、磁盤/CPU/記憶體/連接池等資源耗盡等等,如何確保系統的行為(包括業務邏輯、以及系統自保護措施如降級熔斷等)在所有的情況下都是符合預期的?今天我們的線上演練(本質上也是一種例外測驗))已經做了很多了,如何把更多的問題提前到線下來發現?對于一個復雜的分布式系統來說,要遍歷所有可能出現例外的地方和所有可能出現的例外,例外用例的數量是非常大的,此外,某些例外情況下,系統對外表現出來的行為應該沒有變化;而另一些例外情況下,系統行為是會有變化的,對于后一類,如何給出每一個例外用例的預期結果(即test oracle),也是比較有難度的,
十一 并發測驗(Concurrency Test)
并發(concurrency)可能出現在各個level:資料庫層面,對同一張表、同一條記錄的并發讀寫;單系統層面,同一個行程內的多個執行緒之間的并發,單服務器上的多個行程之間的并發,以及單個服務的多個實體之間的并發;業務層面,對同一個業務物件(會員、單據、賬戶等)的并發操作,等等,傳統的并發測驗是基于性能測驗來做的,有點靠撞大運,而且經常是即便跑出問題來了也會被忽視或者無法repro,并發測驗領域,我接觸過的一些成果包括Microsoft的CHESS以及阿里的譚錦發同學在探索的分布式模型檢查&SST搜索演算法,
十二 回滾的測驗
安全生產三板斧宣傳了多年,在阿里經濟體內大家都能做到“可回滾”了,但我所觀察到的是:很多時候我們有回滾的能力,但是對回滾后系統的正確性,事前保障的手段還不夠,我們更多的是靠灰度和監控等事后手段來確保回滾不會回滾出問題來,事實上,過去兩年,我自己已經親身經歷過好幾次回滾導致的線上故障,回滾測驗的難度在于:需要覆寫的可能性非常多,一個發布可能在任何一個點上回滾,回滾可能還會引發兼容性問題:新代碼生成的資料,在新代碼被回滾后,老代碼是否還能正確的處理這些資料,
十三 兼容性測驗
代碼和資料的兼容性問題有很多形式,例如,如何確保新代碼能夠正確的處理所有的老資料?有時候,老資料是幾個月前的老代碼產生的,例如,一個正向支付單據可能會到幾個月以后才發生退款退票,有時候,老資料可能就是幾分鐘前產生的:用戶的一個操作,背后的流程執行到中間的時候代碼被升級了,驗證這些場景下的兼容性的難度在于:需要驗證的可能性太多了,今天的退款請求對應的正向單據,可能是過去很多個版本的代碼產生的,一個業務流程執行到中間具體什么地方代碼被升級了,可能性也非常多,
例外測驗、并發測驗、回滾測驗、兼容性測驗,這些問題的一個共同點是:我們知道這些問題是可能存在的,但要測的話,需要測的可能性又太多,
十四 Mock
測驗的有效性也依賴于mock的正確性,既然是mock,它和被mock的服務(包括內部的、二方的和三方的)的行為就多多少少會有差異,這種差異就有可能導致bug被漏過,前人也為此想出了“流量比對”等辦法,我曾經有另一個想法:“一鴨三吃”,也就是說,通過bundle和compiler instruction等方法,讓同一套源代碼支持三種不同的編譯構建模式:
-
正常模式:這就是和今天的編譯構建是一樣的,產出的構建物是拿去生產環境跑的,
-
Mock模式:這個模式編譯出來的就是該服務的一個mock,但由于是同一套代碼編譯出來的,最大可能的保留了原來的業務邏輯,做到最大限度的仿真,而且由于是同一套代碼編譯出來的,后期也不會有“脫鉤”的擔心,應用代碼里的業務邏輯變化都能及時反映在mock里,大大減少mock的人肉維護作業量,
-
壓測模式:這個模式編譯出來的也是一個mock,但這個mock是用來給(上游)做性能測驗用的,過去在線下的性能壓測中經常遇到的情況是:我們想要壓的系統還沒到瓶頸,這個系統的下游系統(往往是一個測驗環境)反而先到瓶頸了,壓測模式編譯出來的這個mock犧牲了一部分的業務邏輯仿真,但能確保這個mock本身性能非常好,不會成為性能瓶頸(但對lantency仍然是仿真的),
這個“一鴨三吃”的想法so far還停留在想法層面,我還一直沒有機會實踐一下,
十五 靜態代碼分析
有一些型別的問題,要用通常意義上的軟體測驗來發現,難度和成本很高,但反而是通過靜態代碼分析來發現反而比較容易,例如,ThreadLocal變數忘記清除,會導致記憶體溢位、會導致關鍵資訊在不同的不同的上游請求之間串錯,另一個例子是NullPointerException,一種做法是通過fuzz testing、例外測驗等手段來暴露代碼里的NPE缺陷,以及可以在執行測驗回歸的時候觀察log里面的NPE,但我們也可以通過靜態代碼分析,更早的就發現代碼里面可能存在的NPE,有一些并發問題也可以通過靜態代碼分析來早期準確發現,總之,我們希望盡可能多的通過靜態代碼分析來防住問題,
十六 形式化驗證(Formal Verificaition)
除了在協議、芯片、關鍵演算法等上面的運用以外,形式化方法在更偏業務的層面是否有運用的價值和可能?
十七 防錯設計(Mistake Proof)
嚴格來說,防錯設計并不是software testing范疇內的,但做測驗做久了就發現,有很多bug、很多故障,如果設計的更好一點,就壓根不會發生(因此也就談不上需要測驗了),去年我總結了一下支付系統的防錯設計,后面希望能看到在各類軟體系統形態下的防錯設計原則都能總結出來,另外,最好還能有一些技識訓的手段來幫助更好的落地這些防錯設計原則,這個難度可能比總結設計原則的難度更高,
十八 可測性(Testability)
雖然目前大部分開發和QA同學都知道“可測性”這么件事情,但對可測性把握的還不夠體系化,很多同學覺得可測性就是開介面、加test hook,或者,還沒有很好的理解可測性這個東西落到自己這個領域(例如支付系統、公有云、ERP)意味著什么,在需求和系統設計分析階段還不能做到很有效很有體系的從可測性角度提出要求,往往要求比較滯后,我希望可測性設計可以總結出一系列像程式設計的DRY、KISS、Composition Over Inheritance、Single Responsibility,Rule of Three等設計原則,總結出一系列的反模式,甚至出現像《設計模式》那樣的一本專門的著作,
以上就是我會加到Hard Problems in Test串列的問題,也是我已經或打算投入精力解決的問題,
注:
[1] 我作業中還有一些其他的測驗難題,那些問題在這里沒有列出來,因為那些問題和特定的業務場景或者技術堆疊的相關度比較高,還有一些測驗領域的挑戰,難度也很高,例如,回歸測驗達到99%以上通過率、主干開發以及做到通過代碼門禁的code change就是可以進入發布的,這些也非常有難度,但難度主要是是偏工程的而不是軟體測驗技術本身,
[2] 測驗充分度的度量和提升是兩個問題,有一種觀點認為,測驗充分的度量和提升其實是一件事情,用同樣的演算法分析資料可以進行度量,也能用同樣的演算法來基于資料進行測驗充分度的提升,我不同意這個觀點,度量和提升未必是同一個演算法,這樣的例子非常多了:測驗有效性的度量和提升、運維穩定性的度量和提升,等等,即便度量和提升可以用同一種演算法,我也希望可以盡量再找一些其他方法,盡量不要用同一種演算法又做度量又做提升,因為這樣容易“倍訓”和產生盲區和,
[3] 當然,這句話今天可能不再是那樣了,但那是十幾年前,那時候的在線廣告和大資料還沒到今天這個水平,
[4] 具體形式上,這個資料銀行無需是一個平臺,它不一定是一個服務,它也不一定需要有UI,它可以就是一個jar包,它可以就是在測驗執行時launch的一個單獨的行程,
作者:鄭子穎
本文來自博客園,作者:古道輕風,轉載請注明原文鏈接:https://www.cnblogs.com/88223100/p/18-Challenges-in-Software-Testing.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/530553.html
標籤:其他
下一篇:遺留代碼處理技巧與案例演示
