大家好,我是狂師。
大家在日常開展自動化測試工作時,為了保證介面測試的有效性,少不了要對介面返回的響應欄位進行校驗
、斷言
等操作。當介面返回的欄位數量本身就很少時,介面斷言操作一般都很容易就能實現,但當介面的返回欄位特別多,結構特別複雜時,例如響應欄位數量達到了成百上千時,如何快速實現全部返回欄位的校驗?這類問題,相信困擾了很多的正在開展介面測試的小夥伴。
今天針對如何快速稽核介面返回值全部欄位問題,分享一些解答思路,希望能幫到大家~
其實解決上述之類問題,市面上常見的解決方案有兩類:
- 根據業務校驗需求,自定義開發校驗規則庫
- 藉助現有的第三方庫
今天,我們先來聊聊,如何藉助現有的第三方庫來解決: 快速校驗API介面返回的全部欄位。由於當今大部分介面都是基於Restful API,後續我介紹中,我們假設介面響應體格式以JSON為例。
要滿足上面的實現需求,第三方庫方案有很多,比如常見的就有:deepdiff
、difflib
、json-diff
、json_tools
等,這些三方庫之間,都有各自側重點,本篇文章,重點介紹:如何藉助DeepDiff庫來解決快速校驗介面返回欄位的問題。
一、認識一下,DeepDiff 介紹
Deepdiff
模組常用來校驗兩個物件是否一致,並找出其中差異之處。其中提供了三個類,DeepDiff,DeepSearch和DeepHash,官網地址:https://deepdiff.readthedocs.io/en/latest/ ,當前最新版本為:V5.5.0
主要組成部分:
-
DeepDiff:比較兩個物件,物件可以是欄位、字串等可迭代的物件,針對物件的深層差異,遞迴查詢所有更改。
-
DeepSearch:在物件中搜尋其他物件
-
DeepHash:根據物件的內容進行雜湊處理
DeepDiff 的初衷是用來找出不同資料的差別,可以比較JSON
、XML
文字類的,也可以比較圖片
,在使用了一下之後,其實我們完全可以直接使用它作為測試的斷言,這也是從另一個思考角度提供了一種全新的校驗思路。
二、DeepDiff 使用
當你看完上述的介紹,相信還是一臉懵,不知如何下手,接下來,就通過幾個案例來進一步感受一下Deepdiff
的功能和作用。
使用之前,先安裝:
pip install deepdiff
2.1 案例一:比較兩個JSON
利用Deepdiff 比較 JSON 的差異:
import pytest
import requests
import pprint
from deepdiff import DeepDiff
class TestDemo(object):
def test_case_01(self):
a = {"Object": {
"code": "0",
"message": "success"
},
"code": "0",
"message": "success"
}
b = {"Object": {
"code": "0",
"message": "failure"
},
"message": "success",
"timestamp": "1614301293"
}
pprint.pprint(DeepDiff(a, b))
上述案例,作用是比較a和b兩者的差異,result 差異的輸出結果是:
.{'dictionary_item_added': [root['timestamp']],
'dictionary_item_removed': [root['code']],
'values_changed': {"root['Object']['message']": {'new_value': 'failure',
'old_value': 'success'}}}
上述輸出結果中,實際上根據這個返回的 json 獲取所有的差別。主要是對比物件之間的值、型別前後之間的變化以及刪除的或者增加的情況key進行了結果輸出。
主要包含以下四種情況:
- 1、type_changes:型別改變的key
- 2、values_changed:值發生變化的key
- 3、dictionary_item_added:字典key新增
- 4、dictionary_item_removed:欄位key刪除
2.2 案例二:比較介面響應
有了案例一的基礎,進一步,我們將本地定義寫死的變數值改成採取呼叫介面的方式(更符合實際介面測試),通過發起請求,獲取響應、並結合Deepdiff來斷言使用。
核心思路:先定義預期的響應結構體(意味著,你得事先知道你期望的結果是什麼),再根據實際返回的結構體兩者通過Deepdiff
進行自動比較。
import pytest
import requests
import pprint
from deepdiff import DeepDiff
class TestDemo(object):
def setup_class(self):
self.response = requests.get('http://www.httpbin.org/json').json()
def test_case_02(self):
expected_reps = {'slideshow': {'author': 'Yours Truly', 'date': 'date of publication',
'slides': [{'title': 'Wake up to WonderWidgets!', 'type': 'all'}, {
'items': ['Why <em>WonderWidgets</em> are great',
'Who <em>buys</em> WonderWidgets'], 'title': 'Overview',
'type': 'all'}], 'title': 'Sample Slide Show'}}
pprint.pprint(DeepDiff(expected_reps, self.response))
由於實際返回的結構體和預期定義待校驗的結構體資料完全一樣,因此上述程式碼輸出結果為:{},即兩者沒有差異
。(也意味著實際和預期結果一致)
在此基礎上,如果我們把上述expected_reps
預期結構體中的author
由Yours Truly
修改為Yours
,再執行一次,則輸出的結果為:
{'values_changed': {"root['slideshow']['author']": {'new_value': 'Yours Truly',
'old_value': 'Yours'}}}
從上述的輸出結果中,我們可以很明顯的獲取到三個訊息:
- 介面返回的結構體中有值發生了改變,通過
values_changed
標識出來了 - 明確指出具體哪個欄位的值發生改變了,如
root['slideshow']['author']
。 - 改變具體的內容,如實際返回值為
Yours Truly
,而預期值為Yours
。
看完了這個,相信此時的你,對Deepdiff在介面測試中的使用,已經有了一些感覺了。但接著你肯定會提出疑問,有些介面返回的值,並不是固定的,那比如校驗呢。比如某個時間戳欄位,每次呼叫介面時,返回欄位的值都是不一樣,針對這類只知道資料規則,但資料本身的值一開始是無法確定的,又該如何結合Deepdiff來使用呢?別急,再接著往下看。
2.3 案例三:正則搜尋匹配
要解決上述的問題,可以利用DeepSearch
中的正則搜尋匹配功能,如果你的介面返回,是一個很深的巢狀結構物件,然後你想校驗查詢指定的元素(key和value都行)是否存在,那麼Deep Search將是個好選擇。
使用前,需要先匯入from deepdiff import grep
,示例原始碼如下:
def test_case_03(self):
datas = {"mikezhou": "狂師", "age":18, "city": "china", "info": {"author": {"tag": "mikezhou"}}}
reuslt = datas | grep("mike*",use_regexp=True)
print(reuslt)
比如想校驗有沒有以mike開頭欄位或值在返回的結構體中,指定元素存在則返回它的路徑;不存在則返回一個空字典。上述輸出結果如下:
.{'matched_paths': ["root['mikezhou']"], 'matched_values': ["root['info']['author']['tag']"]}
上述示例雖簡單,但如果你足夠聰明,相信應該已經能從中Get核心思路了:針對一些動態事先無法預料的值,可以通過藉助正規表示式來匹配校驗,具體如何校驗,取決於你的正規表示式如何描述。
三、最後一個小技巧:DeepDiff 黑名單
在實際做介面測試斷言時,有時物件順序不一樣,但是實際情況兩個值還是一樣的,或者是針對全量欄位校驗時,想跳過一些特殊的欄位校驗(類似黑名單一樣,將不需要校驗的欄位,明確指出),為了解決這類問題,Deepdiff也提供了相信的引數,只需要在比較的時候加入:
ignore order
(忽略排序)ignore string case
(忽略大小寫)exclude_paths
欄位黑名單排除引數即可,原型如下:
result = DeepDiff(result, expected, view='tree',ignore_order=True,ignore_string_case=True)
示例:
def test_case_05(self):
expected_reps = {"datas": {
"code": "200",
"message": "success"
},
"code": "201",
"message": "success"
}
actual_reps = {"datas": {
"code": "201",
"message": "failure"
},
"message": "Success",
"timestamp": "1614301293"
}
pprint.pprint(DeepDiff(expected_reps, actual_reps, ignore_order=True,ignore_string_case=True,exclude_paths={"root['timestamp']"}))
上述示例程式碼,忽略了排序規則、大小寫問題,並且指定排除timestamp
欄位校驗。具體的輸出結果如下:
{'dictionary_item_removed': [root['code']],
'values_changed': {"root['datas']['code']": {'new_value': '201',
'old_value': '200'},
"root['datas']['message']": {'new_value': 'failure',
'old_value': 'success'},
"root['message']": {'new_value': 'Success',
'old_value': 'success'}}}
四、小結
通過上述的案例介紹,相信你對DeepDiff
的使用有了一個基本認識。在介面自動化測試中,小結一下,使用 DeepDiff 的好處有:
- 介面測試的時候,可以直接利用預期結構體(或者稱之為介面契約)與實際返回的結構體(欄位、值)進行自動比較,來確定是不是一樣,可以少寫很多程式碼。
- 資料庫資料比較的時候也是一樣可以,使用 SQL查出結果之後,直接變成 JSON就可以和期望的 JSON對比了。
本文旨在從另一個視角幫大家提供一些介面測試全量欄位校驗的解決思路,更多關於Deepdiff
使用技巧,以及更優解的全量欄位解決方案(還有很多),歡迎大家積極探索。