背景:
- 接口自動化測試實現簡單、成本較低、收益較高,越來越受到企業重視
- restful風格的api設計大行其道
- json成為主流的輕量級數據交換格式
痛點:
- 接口關聯
- 也稱為關聯參數。在應用業務接口中,完成一個業務功能時,有時候一個接口可能不滿足業務的整個流程邏輯,需要多個接口配合使用,簡單的案例如:B接口的成功調用依賴於A接口,需要在A接口的響應數據(response)中拿到需要的字段,在調用B接口的時候,傳遞給B接口作為B接口請求參數,拿到后續響應的響應數據。
- 接口關聯通常可以使用正則表達式去提取需要的數據,但對於json這種簡潔、清晰層次結構、輕量級的數據交互格式,使用正則未免有點殺雞用牛刀的感覺(是的,因為我不擅長寫正則表達式),我們需要更加簡單、直接的提取json數據的方式。
- 數據驗證
- 這里的數據驗證指的是對響應結果進行數據的校驗
- 接口自動化測試中,對於簡單的響應結果(json),可以直接和期望結果進行比對,判斷是否完全相等即可。如
json {"status":1,"msg":"登錄成功"}
- 對於格式較復雜,尤其部分數據存在不確定性、會根據實際情況變化的響應結果,簡單的判斷是否完全相等(斷言)通常會失敗。如:
json {"status":1,"code":"10001","data":[{"id":1,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"1","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-05-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":2,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"2","unfinishedInterest":"1.0","unfinishedPrincipal":"0","repaymentDate":"2018-06-27 12:24:01","actualRepaymentDate":null,"status":"0"},{"id":3,"investId":"1","createTime":"2018-04-27 12:24:01","terms":"3","unfinishedInterest":"1.0","unfinishedPrincipal":"100.00","repaymentDate":"2018-07-27 12:24:01","actualRepaymentDate":null,"status":"0"}],"msg":"獲取信息成功"}
上面的json結構嵌套了很多信息,完整的匹配幾乎不可能成功。比如其中的createTime信息,根據執行接口測試用例的時間每次都不一樣。同時這個時間是響應結果中較為次要的信息,在進行接口自動化測試時,是可以選擇被忽略的。 - 我們需要某種簡單的方法,能夠從json中提取出我們真正關注的信息(通常也被稱為關鍵信息)。如提取出status的值為1,data數組中每個對象的investId都為1,data中第三個對象的unfinishedPrincipal值為100.00,只要這三個關鍵信息校驗通過,我們就認為響應結果沒有問題。
解決方案
JsonPath可以完美解決上面的痛點。通過JsonPath可以從多層嵌套的Json中解析出所需要的值。
JsonPath
- JsonPath參照XPath解析xml的方式來解析Json
- JsonPath用符號$表示最外層對象,類似於Xpath中的根元素
- JsonPath可以通過點語法來檢索數據,如:
shell $.store.book[0].title
- 也可以使用中括號[]的形式,如
shell $['store']['book'][0]['title']
運算符(Operators)
運算符 | 說明 |
---|---|
$ | 根元素 |
@ | 當前元素 |
* | 通配符,可以表示任何元素 |
.. | 遞歸搜索 |
. | 子節點(元素) |
['' (, '')] | 一個或者多個子節點 |
[ (, )] | 一個或者多個數組下標 |
[start:end] | 數組片段,區間為[start,end) |
[?()] | 過濾器表達式,其中表達式結果必須是boolean類型,如可以是比較表達式或者邏輯表達式 |
JsonPath案例
json
{"lemon":{"teachers":[{"id":"101","name":"華華","addr":"湖南長沙","age":25},{"id":"102","name":"韜哥","age":28},{"id":"103","name":"Happy","addr":"廣東深圳","age":16},{"id":"104","name":"歪歪","addr":"廣東廣州","age":29}],"salesmans":[{"id":"105","name":"毛毛","age":17},{"id":"106","name":"大樹","age":27}]},"avg":25}
JsonPath例子及說明
JsonPath | 路徑說明 |
---|---|
$.lemon.teachers[*].name | 獲取所有老師的的名稱 |
$..name | 獲取所有人的名稱 |
$.lemon.* | 所有的老師和銷售 |
$.lemon..age | 所有人的年齡 |
$..age | 所有人的年齡 |
$.lemon.teachers[*].age | 所有老師的年齡 |
$.lemon.teachers[3] | 索引為3(第4個)老師的信息 |
$..teachers[3] | 索引為3(第4個)老師的信息 |
$.lemon.teachers[-2] | 倒數第2個老師的信息 |
$..teachers[-2] | 倒數第2個老師的信息 |
$..teachers[1,2] | 第2到第3個老師的信息 |
$..teachers[:2] | 索引0(包含)到索引2(不包含)的老師信息 |
$..teachers[1:3] | 索引1(包含)到索引3(不包含)的老師信息 |
$..teachers[-2:] | 最后的兩個老師的信息 |
$..teachers[2:] | 索引2開始的所有老師信息 |
$..teachers[?(@.addr)] | 所有包含地址的老師信息(jsonpath_rw不支持) |
$.lemon.teachers[?(@.age < 20)] | 所有年齡小於20的年齡信息(jsonpath_rw不支持) |
一:使用jsonpath
安裝jsonpath模塊
pip install jsonpath==0.75
解析
# 1:導入相關模塊
import json
import jsonpath
# 2: 准備json字符串
jsonStr = ''' { "lemon": { "teachers": [ { "id": "101", "name": "華華", "addr": "湖南長沙", "age": 25 }, { "id": "102", "name": "韜哥", "age": 28 }, { "id": "103", "name": "Happy", "addr": "廣東深圳", "age": 16 }, { "id": "104", "name": "歪歪", "addr": "廣東廣州", "age": 29 } ], "salesmans": [ { "id": "105", "name": "毛毛", "age": 17 }, { "id": "106", "name": "大樹", "age": 27 } ] }, "avg": 25 } '''
# 3:加載json字符串為json對象
json_obj = json.loads(jsonStr)
# 4:使用jsonpath模塊的jsonpath方法提取信息
# eg1: 提取所有包含addr屬性的老師信息,結果為list類型
results = jsonpath.jsonpath(json_obj,"$..teachers[?(@.addr)]")
print(results)
# 輸出結果:[{'id': '101', 'name': '華華', 'addr': '湖南長沙', 'age': 25}, {'id': '103', 'name': 'Happy', 'addr': '廣東深圳', 'age': 16}, {'id': '104', 'name': '歪歪', 'addr': '廣東廣州', 'age': 29}]
# eg2:提取所有年齡小於20歲的老師的name,結果為list類型
results2 = jsonpath.jsonpath(json_obj,"$.lemon.teachers[?(@.age < 20)].name")
print(results2)
# 輸出結果為:['Happy']
二:使用jsonpath_rw
安裝jsonpath_rw模塊
pip install jsonpath-rw
解析
# 1:導入相關模塊
import json
from jsonpath_rw import jsonpath, parse
# 2: 准備json字符串
jsonStr = ''' # 同上(略) '''
# 3:加載為json對象
json_obj = json.loads(jsonStr)
# 4:采用parse創建jsonpath對象(該案例是得到所有的老師name)
jsonpath_expr = parse('$.lemon.teachers[*].name')
# 5:通過jsonPath檢索json后返回匹配的數據,類型是DatumInContext的list
datumInContexts = jsonpath_expr.find(json_obj)
# 采用列表推導式檢索出所有匹配的值
values = [datum.value for datum in datumInContexts]
print(values)
# 輸出結果為:['華華', '韜哥', 'Happy', '歪歪']
# 案例2:提取索引為4的老師的name
jsonpath_expr = parse('$.lemon.teachers[3].name')
datumInContexts = jsonpath_expr.find(json_obj)
print(datumInContexts)
values = [datum.value for datum in datumInContexts]
print(values)
# 結果為:['歪歪']
更多jsonpath_rw用法參考:
https://www.cnblogs.com/aoyihuashao/p/8665873.html