更新時間:2018-10-26 來源:黑馬程序員技術(shù)社區(qū) 瀏覽量:
模塊級別自動化測試的經(jīng)驗與教訓(xùn)概述
搞了幾個月的自動化測試,結(jié)果不甚理想,這里做一個簡單的總結(jié)。
為什么要做自動化測試呢?因為手工測試效率低,找 case、執(zhí)行 case 太費時間。
為什么自動化測試前面要加“模塊級別”呢?因為一個系統(tǒng)依賴很多外部系統(tǒng),如果不能有效屏蔽外部環(huán)境的差異,自動化測試會經(jīng)常因為環(huán)境問題而失敗。
原理
這里講的自動化測試原理是,在線上環(huán)境錄制測試 case,在線下測試環(huán)境利用錄制的 case 進行回放測試。
如何錄制 case?某個系統(tǒng)對一次請求的一次處理可以認為是一個 case,模塊級別的自動化測試要求錄制該系統(tǒng)在處理請求時的所有對外交互,包括但不限于:
該模塊接收到的請求和對外響應(yīng)信息。該模塊在處理請求過程中,產(chǎn)生的所有下游調(diào)用的請求和響應(yīng)。常見的有:HTTP 請求、DB 請求、Redis 查詢、MQ 輸出、熱更新配置等等。
以下圖為例,有 A、B、C、D 四個系統(tǒng)模塊,假設(shè)要對模塊 A 做自動化測試,需要錄制測試 case。該模塊有一個接口,接收來自模塊 D 的請求,在處理過程中會產(chǎn)生對模塊 B、C 的下游調(diào)用。那么在錄制 case 時,不僅要錄制該接口的輸入輸出,也有錄制兩次下游調(diào)用的輸入和輸出。
在請求處理過程中,往往會出現(xiàn)各種線程的切換、異步調(diào)用,如何將一個請求的所有對外交互串起來呢?很多公司都有分布式鏈路跟蹤系統(tǒng),會有一個 traceID 能夠把不同系統(tǒng)的請求串聯(lián)起來,同理也可以利用該 traceID 把單個系統(tǒng)內(nèi)部的請求處理過程串聯(lián)起來,擁有同一個 traceID 的輸入輸出可以認為是屬于同一個 case 的。
case 的錄制思路比較簡單,收集數(shù)據(jù)后存儲到 DB 中即可。case 的回放測試分為兩步:
運行測試 case。DIFF 測試結(jié)果,需要進行 DIFF 的數(shù)據(jù)包括測試接口的響應(yīng)參數(shù)、下游調(diào)用的請求參數(shù),也就是被測模塊對外輸出的數(shù)據(jù)。
經(jīng)歷的坑本地緩存的 dump 問題
前面說了,錄制 case 時,需要錄制所有的下游調(diào)用的請求和響應(yīng)。對于需要定期更新的本地緩存,也需要 dump 其數(shù)據(jù),如何 dump 呢?dump 查緩存的方法即可,以下面代碼為例,dump 方法 getXXXConfig 的輸入輸出即可。本質(zhì)上是將 getXXXConfig 方法的調(diào)用當(dāng)成了外部請求進行錄制。
@Component
public class XXXCache {
private LoadingCache<String, List<XXX>> xxxCache = CacheBuilder.newBuilder().maximumSize(10000).expireAfterWrite(5, TimeUnit.MINUTES).build(
new CacheLoader<String, List<XXX>>() {
public List<XXX> load(String key) {
// 省略
}
});
public List<XXX> getXXXConfig(String param) {
try {
return xxxCache.get(param);
}catch (Exception e) {
logger.error("get xxx error, param:{}", e, param);
}
return ListUtils.EMPTY_LIST;
}
}
性能問題
收集數(shù)據(jù)必定會耗費一些性能,為了減輕對系統(tǒng)的壓力,錄制時增加了采樣率的概念,只錄制少量數(shù)據(jù)。
多數(shù)情況下,降低采樣率可以解決性能問題,但也有例外。在對接某個系統(tǒng)時發(fā)現(xiàn)其有性能問題,CPU 從 20% 漲到了 30%,發(fā)現(xiàn)一次請求里要錄制 5000 條下游調(diào)用,我們將采樣率降低到 10 分鐘錄制一個 case 依然無法解決問題。那么到底是哪里耗費性能呢?研究后發(fā)現(xiàn),是在判斷是否收集數(shù)據(jù)的方法里比較耗費性能,該方法在每次有下游調(diào)用時都需要用來判斷是否需要收集數(shù)據(jù)。該方法代碼如下所示,其中有兩點影響性能:
在低并發(fā)的情況下,ConcurrentHashMap 競爭較少影響不大,然而在高并發(fā)情況下競爭較多,性能下降。
inRecordEnv 是一個 volatile 變量,會引起 CPU 緩存失效,少量影響不大,但是一次請求里 5000 次 CPU 緩存失效影響就大了。
public volatile boolean inRecordEnv;
public ConcurrentHashMap<String, TraceContext> recordingTraceIdMap;
public boolean needRecord(String traceId) {
if (! inRecordEnv) {
return false;
}
return recordingTraceIdMap.contains(traceId);
}
解決該問題的辦法有兩個:
將是否需要錄制放到線程本地變量中,跟 traceID 一起傳遞,這需要改到分布式鏈路跟蹤組件。
case 錄制一般是選擇集群中的某一臺服務(wù)器進行錄制的,那么修改負載均衡機制,降低錄制 case 集群的權(quán)重,即可減輕其壓力。同時,需要稍作處理,讓其他機器需要不走錄制判斷邏輯,畢竟單單是錄制判斷邏輯就足以影響性能了。
靜態(tài)方法、隨機數(shù)問題
有一些緩存的查詢用的是靜態(tài)方法,靜態(tài)方法因為不被 Spring 容器托管,是無法使用 Spring AOP 處理的。對于此類方法,如果要 dump 數(shù)據(jù),就必須使用 java instrument 字節(jié)碼修改技術(shù)。
與之類似的還有隨機數(shù)生成器、UUID 生成,這些隨機數(shù)往往是某個 Redis 值的 key,也需要去 mock 住才能保證測試成功。
帶異步任務(wù)的請求結(jié)束時間判斷問題
在業(yè)務(wù)處理中,為了提升處理速度,經(jīng)常會啟動多個線程同時處理,處理完畢后再合并請求結(jié)果返回。
在一些特殊情況下,業(yè)務(wù)處理過程中,會啟動異步任務(wù)去計算,主線程不等待異步任務(wù)結(jié)束便返回結(jié)果。而該異步任務(wù)的結(jié)果會存在分布式緩存 Redis 中,供下一階段業(yè)務(wù)處理使用。問題產(chǎn)生在原因是,我們需要錄制主線程和異步任務(wù)的數(shù)據(jù),但是難以判斷請求的處理何時真正結(jié)束。一般都是在接口返回數(shù)據(jù)給調(diào)用方時,就認為請求處理結(jié)束了,此 case 的數(shù)據(jù)錄制完成。而這里需要等待異步任務(wù)結(jié)束,才算真正錄制完成。
解決辦法是:新增任務(wù)標(biāo)記 API,用于標(biāo)記異步任務(wù)的啟動和結(jié)束,在業(yè)務(wù)代碼中進行標(biāo)記。
代碼規(guī)范問題
在業(yè)務(wù)系統(tǒng)中引入模塊級自動化測試時,發(fā)現(xiàn)一些通用的問題:
一些 Bean 缺少默認構(gòu)造函數(shù):因為涉及到 Mock 數(shù)據(jù)的反序列化,需要這些 Bean 有默認構(gòu)造函數(shù)。traceID 傳遞過程中丟失:因為錄制請求依賴 traceID 來判斷是否錄制,所以在線程切換、異步回調(diào)時,必須保證 traceID 的正確傳遞。TimeStamp 問題:一些請求參數(shù)里會有當(dāng)前時間的信息,時間信息在回放時,會發(fā)生變化,導(dǎo)致測試 DIFF 失敗。解決辦法有兩個,一是測試時修改系統(tǒng)時間,此方法存在精確度的問題,二是忽略時間字段的 DIFF。
總結(jié)
模塊級自動化測試的思路是:在線上環(huán)境錄制 case,通過錄制請求處理過程中的所有對外交互,實現(xiàn)對外部環(huán)境依賴的解耦。在線下測試環(huán)境回放測試 case,并且 DIFF 對于對外輸出。
在業(yè)務(wù)系統(tǒng)接入過程中,遇到的問題,一些是代碼規(guī)范問題,一些是特殊業(yè)務(wù)邏輯造成的問題。其中,第二類問題嚴重影響接入效率。
本文版權(quán)歸軟件測試培訓(xùn)學(xué)院所有,歡迎轉(zhuǎn)載,轉(zhuǎn)載請注明作者出處。謝謝!
作者:軟件測試培訓(xùn)學(xué)院
首發(fā):http://m.ko1818.cn/special/testzly/index.html