本文章轉(zhuǎn)載于搜狗測試
問題背景:
在測試&部署監(jiān)控過程中,我們常常會遇到外部接口返回數(shù)據(jù)不靠譜的時候。最常見的場合是從某個http獲取如json和xml等結(jié)構(gòu)化的結(jié)果,進行解析并處理,在這時候出現(xiàn)以下這幾種常見類型的錯誤:
(1)整個結(jié)構(gòu)不完整。直接無法解析json/xml。
(2)編碼錯誤,常見的gbk/utf8錯誤
(3)超長數(shù)據(jù)/非法字符。
(4)數(shù)據(jù)類型不匹配。需要是數(shù)字的給了字符串,該是數(shù)組的給了字符串等,對json本身來說沒問題,程序處理就會錯誤或者崩潰。
(5)字段缺失或者為空,這個情況對json本身來說也是沒問題的,處理進程固定要去取這里的字段就會出問題,或者進程本身沒問題,但實際展現(xiàn)出問題。
例如json描述一個商品最近30天的售價,提供一個數(shù)組里有30個數(shù)據(jù)來畫點,json里這個數(shù)組為空,從數(shù)據(jù)格式上來說沒問題,但實際畫點時展現(xiàn)即為空。
截圖是來自一份合作方的數(shù)據(jù),箭頭指向的是上證指數(shù)曲線的點,如果點數(shù)據(jù)完全缺失(為空)則畫曲線的界面會顯示為空。在json結(jié)構(gòu)上則仍然驗證為合法。
解決問題的現(xiàn)狀:
對上述問題,我們有一些簡單的自動化監(jiān)控手段,通過定期抓取http接口再獲取其中內(nèi)容這一步比較簡單,接下來我們會驗證http狀態(tài)碼(200正常,非200認(rèn)為是有問題)和長度,如果過短(例如少于20字節(jié))則認(rèn)為是無效。
還有一些自動化case會先人工看一下接口返回的具體內(nèi)容,然后再作為case檢查點,檢查抓取接口中是否存在固定字符串片段,以此進一步驗證返回結(jié)果的正確性。這里我們?nèi)匀贿M行的是字符串粒度上的檢查。
解決問題思考:
如果只通過字符串的方式檢測長度,這個粒度過于粗糙,難以發(fā)現(xiàn)上述詳細一些問題。檢查固定的字符串片段存在,能部分彌補上述不足,但仍然無法檢查背景中提到的諸如字段缺失的問題。
但是若要檢查字符串完全相同,則要求接口每次返回的數(shù)據(jù)完全相同,和實際應(yīng)用情況不符,維護case難度大。那么,我們的思路就轉(zhuǎn)向如何對數(shù)據(jù)結(jié)構(gòu)體進行檢查。
總的思路出于以下幾條:
1.根據(jù)歷史接口請求結(jié)果作為范本,提取特征作為約束對新數(shù)據(jù)進行檢查。提高工具本身的適用范圍。
2.接口返回的數(shù)據(jù)應(yīng)該可以化為結(jié)構(gòu)體,通過對結(jié)構(gòu)體字段的條件約束來判斷數(shù)據(jù)檢查是否通過。
3.對有些字段內(nèi)容可以進行二次解析,保障其中數(shù)據(jù)的合法性。
4.基于歷史數(shù)據(jù)特征進行統(tǒng)計,輔助對數(shù)據(jù)字段進行正確性檢查,進一步降低維護條件約束的難度。
接下來我們逐步來設(shè)計我們以上思考功能。
解決問題的設(shè)計:
0.用什么代碼?
現(xiàn)在任何一種主流語言都有成熟的json/xml解析庫,幸運的是作為正確性檢查,這個規(guī)模我們基本不需要考慮性能問題。當(dāng)速度不可接受的時候可以考慮多進程分開跑來環(huán)節(jié)壓力。
推薦腳本語言python/php,開發(fā)起來更快一些。作為一個輕量的檢查工具甚至只是個lib,它應(yīng)該盡量簡單,方便使用,方便納入現(xiàn)有的測試框架之中。
1.如何抽取特征?
無論是json還是xml,本質(zhì)上都是樹形結(jié)構(gòu)?;谖覀冏罱K目的是進行驗證的考慮,把json/xml解析為樹是個不錯的主意。既然預(yù)期結(jié)構(gòu)完全相同,那么我們可以依照key,依次遍歷檢查樹的每個節(jié)點,比對被比較object是否一致。
以json為例,我們輸入兩個json字符串,將其轉(zhuǎn)化為object。如果是復(fù)雜結(jié)構(gòu),則再對object進行遞歸比較,如果是int或者string,則按既定規(guī)則比較。
function compare_json($in_json, $diff_json){
$json = new Services_JSON();
$start_object = $json->decode($json_str_from_file);
$diff_object = $json->decode($json_str_diff_from_file);
return compare_all($start_object, $diff_object);
}
compare_all(obj2) 進行遍歷,先判斷類型再進行比較,返回布爾值檢查結(jié)果向上傳遞。
如圖,我們舉個例子,查詢某個小組成員的數(shù)據(jù),根據(jù)json生成的樹
{
"name":"name1",
"date":123,
"members":[
{
"name":"alice",
"level":5,
"lastpost":{
"title":"noname1",
"date":500
}
},
{
"name":"bob",
"level":0,
"lastpost":{
"title":"noname2",
"date":501
}
}
]
}
對應(yīng)的結(jié)構(gòu)
2.約束條件都有哪些類型?
根據(jù)實際應(yīng)用情況的不同,可以按需求調(diào)整。特別是數(shù)組由于數(shù)量一般不一致,可以考慮在非空的條件下只取第一個進行結(jié)構(gòu)驗證,也可以考慮遍歷取每一個節(jié)點進行相同的結(jié)構(gòu)驗證。
在這里舉一些例子作為常見的檢查參考:
(1)根據(jù)key進行遍歷,如果對照的測試數(shù)據(jù)直接取不到key對應(yīng)的object,則認(rèn)為有問題。在取到數(shù)據(jù)的情況下進行比較:
(2)兩邊數(shù)據(jù)類型一致
(3)樣板數(shù)據(jù)非空的情況下,檢查數(shù)據(jù)應(yīng)該非空
(4)樣板數(shù)據(jù)為空,檢查數(shù)據(jù)為非空或空都可以
(5)數(shù)組里的元素個數(shù)應(yīng)該保持一致
(6)如果不是葉子節(jié)點,它下面還有某種結(jié)構(gòu)的話,用相同規(guī)則處理下面的子樹。
3.對其中個別節(jié)點的附加檢查方式:
我們同樣考慮,在一些case的執(zhí)行中,要求對其中一些重要的節(jié)點做出復(fù)雜檢查操作,這些檢查操作是根據(jù)case來的,不能適用到其他case上。最重要的問題是如何定位節(jié)點。
例如上述每條json數(shù)據(jù)中,希望檢查每個level都在5以上… 通過path定位需要一個輔助工具來索引json返回對應(yīng)的數(shù)據(jù)。
對于xpath for json,php有第三方的lib提供了jsonpath來執(zhí)行類似xpath的檢索功能。通過實驗檢查可行,也可以單獨根據(jù)xpath的語法寫一個解析器,但這里不符合我們對快速實現(xiàn)最終檢查目的這個目標(biāo)。是否自己寫lib來支持,取決于工期的長短。
http://goessner.net/articles/JsonPath/
一個簡單的實例,對每個level進行檢查。
//load library
require_once ('lib/JSON.php');
require_once ('lib/jsonpath-0.8.1.php');
$json_newstr ='{"name": "name1", "date": 123, "members": [{"name":"alice", "level": 5, "lastpost": {"title": "noname1", "date": 500}} , {"name":"bob", "level": 0, "lastpost": {"title": "noname2", "date": 501}}]}';
echo"json str: $json_newstr\n";
$json =newServices_JSON(SERVICES_JSON_LOOSE_TYPE);
$start_object = $json->decode($json_newstr);
$result = jsonPath($start_object,"$..members..level");
echo"got all levels...\n";
var_dump($result);
$check_result_ok =true;
foreach($result as $level_to_check){
if($level_to_check <=0){
$check_result_ok =false;
}
}
var_dump($check_result_ok);
運行上述片段對json字符串進行檢查
task start...
json str: {"name":"name1","date":123,"members": [{"name":"alice","level":5,"lastpost": {"title":"noname1","date":500}} , {"name":"bob","level":0,"lastpost": {"title":"noname2","date":501}}]}
got all levels...
array(2) {
[0]=>
int(5)
[1]=>
int(0)
}
bool(false)
task end. run checkfor0time(s)
我們輸出檢查結(jié)果為false,發(fā)現(xiàn)了一條不符合預(yù)期的數(shù)據(jù)。
4. 如何基于歷史特征進行統(tǒng)計?
在以上部分我們了解如何提取特征后,我們可以考慮將其按照結(jié)構(gòu)化保存在mysql里。在前幾步執(zhí)行過程中我們已經(jīng)遍歷了json對應(yīng)object的樹狀結(jié)構(gòu),以及通過xpath等樹狀描述對節(jié)點進行定位。將這幾個步驟組合起來即可:以接口為key,按需要保存若干特征描述,對后續(xù)提取的結(jié)果進行檢查。
以上面的樣例json為例,我們可以保存每次抓取檢查時,/members[]數(shù)組下的元素個數(shù),當(dāng)和歷史平均值波動大于80%時認(rèn)為是有問題報警。
5. 基于字符串的檢查…
除了上述的詳細檢查外,我們依然需要對字符串進行一些檢查,例如編碼、總長度和結(jié)構(gòu)完整性。最后一點比較簡單,考慮調(diào)用lib時parse失敗,如果失敗說明json/xml結(jié)構(gòu)不完整,保存現(xiàn)場供人工查看,調(diào)整case。
以上就是關(guān)于數(shù)據(jù)接口檢查類的自動化測試思路,在設(shè)計上我們考慮到現(xiàn)在一些自動化監(jiān)控/測試實踐中遇到的問題,和趨向最新機器學(xué)習(xí)思路提取特征的想法提出的想法。