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