檢查數據接口返回數據合法性
問題背景:
在測試&部署監控過程中,我們常常會遇到外部接口返回數據不靠譜的時候。最常見的場合是從某個http獲取如json和xml等結構化的結果,進行解析並處理,在這時候出現以下這幾種常見類型的錯誤:
(1)整個結構不完整。直接無法解析json/xml。
(2)編碼錯誤,常見的gbk/utf8錯誤
(3)超長數據/非法字符。
(4)數據類型不匹配。需要是數字的給了字符串,該是數組的給了字符串等,對json本身來說沒問題,程序處理就會錯誤或者崩潰。
(5)字段缺失或者為空,這個情況對json本身來說也是沒問題的,處理進程固定要去取這裏的字段就會出問題,或者進程本身沒問題,但實際展現出問題。
例如,json描述一個商品最近30天的售價,提供一個數組裏有30個數據來畫點,json裏這個數組為空,從數據格式上來說沒問題,但實際畫點時展現即為空。
截圖是來自一份合作方的數據,箭頭指向的是上證指數曲線的點,如果點數據完全缺失(為空)則畫曲線的界面會顯示為空。在json結構上則仍然驗證為合法。
解決問題的現狀:
對上述問題,我們有一些簡單的自動化監控手段,通過定期抓取http接口再獲取其中內容這一步比較簡單,接下來我們會驗證http狀態碼(200正常,非200認為是有問題)和長度,如果過短(例如少於20字節)則認為是無效。
還有一些自動化case會先人工看一下接口返回的具體內容,然後再作為case檢查點,檢查抓取接口中是否存在固定字符串片段,以此進一步驗證返回結果的正確性。這裏我們仍然進行的是字符串粒度上的檢查。
解決問題思考:
如果只通過字符串的方式檢測長度,這個粒度過於粗糙,難以發現上述詳細一些問題。檢查固定的字符串片段存在,能部分彌補上述不足,但仍然無法檢查背景中提到的諸如字段缺失的問題。
但是若要檢查字符串完全相同,則要求接口每次返回的數據完全相同,和實際應用情況不符,維護case難度大。那麽,我們的思路就轉向如何對數據結構體進行檢查。
總的思路出於以下幾條:
1.根據歷史接口請求結果作為範本,提取特征作為約束對新數據進行檢查。提高工具本身的適用範圍。
2.接口返回的數據應該可以化為結構體,通過對結構體字段的條件約束來判斷數據檢查是否通過。
3.對有些字段內容可以進行二次解析,保障其中數據的合法性。
4.基於歷史數據特征進行統計,輔助對數據字段進行正確性檢查,進一步降低維護條件約束的難度。
接下來我們逐步來設計我們以上思考功能。
解決問題的設計:
0.用什麽代碼?
現在任何一種主流語言都有成熟的
json/xml
解析庫,幸運的是作為正確性檢查,這個規模我們基本不需要考慮性能問題。當速度不可接受的時候可以考慮多進程分開跑來環節壓力。
推薦腳本語言
python/php
,開發起來更快一些。作為一個輕量的檢查工具甚至只是個
lib
,它應該盡量簡單,方便使用,方便納入現有的測試框架之中。
1.如何抽取特征?
無論是json還是xml,本質上都是樹形結構。基於我們最終目的是進行驗證的考慮,把json/xml解析為樹是個不錯的主意。既然預期結構完全相同,那麽我們可以依照key,依次遍歷檢查樹的每個節點,比對被比較object是否一致。
以json為例,我們輸入兩個json字符串,將其轉化為object。如果是復雜結構,則再對object進行遞歸比較,如果是int或者string,則按既定規則比較。
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) 進行遍歷,先判斷類型再進行比較,返回布爾值檢查結果向上傳遞。
如圖,我們舉個例子,查詢某個小組成員的數據,根據json生成的樹
{
"name":"name1",
"date":123,
"members":[
{
"name":"alice",
"level":5,
"lastpost":{
"title":"noname1",
"date":500
}
},
{
"name":"bob",
"level":0,
"lastpost":{
"title":"noname2",
"date":501
}
}
]
}
對應的結構
2.約束條件都有哪些類型?
根據實際應用情況的不同,可以按需求調整。特別是數組由於數量一般不一致,可以考慮在非空的條件下只取第一個進行結構驗證,也可以考慮遍歷取每一個節點進行相同的結構驗證。
在這裏舉一些例子作為常見的檢查參考:
(1)根據key進行遍歷,如果對照的測試數據直接取不到key對應的object,則認為有問題。在取到數據的情況下進行比較:
(2)兩邊數據類型一致
(3)樣板數據非空的情況下,檢查數據應該非空
(4)樣板數據為空,檢查數據為非空或空都可以
(5)數組裏的元素個數應該保持一致
(6)如果不是葉子節點,它下面還有某種結構的話,用相同規則處理下面的子樹。
3.對其中個別節點的附加檢查方式:
我們同樣考慮,在一些case的執行中,要求對其中一些重要的節點做出復雜檢查操作,這些檢查操作是根據case來的,不能適用到其他case上。最重要的問題是如何定位節點。
例如上述每條json數據中,希望檢查每個level都在5以上… 通過path定位需要一個輔助工具來索引json返回對應的數據。
對於xpath for json,php有第三方的lib提供了jsonpath來執行類似xpath的檢索功能。通過實驗檢查可行,也可以單獨根據xpath的語法寫一個解析器,但這裏不符合我們對快速實現最終檢查目的這個目標。是否自己寫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 = new Services_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 check for0 time(s)
我們輸出檢查結果為false,發現了一條不符合預期的數據。
4. 如何基於歷史特征進行統計?
在以上部分我們了解如何提取特征後,我們可以考慮將其按照結構化保存在mysql裏。在前幾步執行過程中我們已經遍歷了json對應object的樹狀結構,以及通過xpath等樹狀描述對節點進行定位。將這幾個步驟組合起來即可:以接口為key,按需要保存若幹特征描述,對後續提取的結果進行檢查。
以上面的樣例json為例,我們可以保存每次抓取檢查時,/members[]數組下的元素個數,當和歷史平均值波動大於80%時認為是有問題報警。
5. 基於字符串的檢查…
除了上述的詳細檢查外,我們依然需要對字符串進行一些檢查,例如編碼、總長度和結構完整性。最後一點比較簡單,考慮調用lib時parse失敗,如果失敗說明json/xml結構不完整,保存現場供人工查看,調整case。
以上就是關於數據接口檢查類的自動化測試思路,在設計上我們考慮到現在一些自動化監控/測試實踐中遇到的問題,和趨向最新機器學習思路提取特征的想法提出的想法。
檢查數據接口返回數據合法性