Python全字段断言之DeepDiff模块详解
作者:shines_m
一、简介
工作中我们经常要两段代码的区别,或者需要查看接口返回的字段与预期是否一致。
Python中也提供了deepdiff库,常用来校验两个对象是否一致,包含3个常用类,DeepDiff,DeepSearch和DeepHash
其中DeepDiff最常用,可以对字典,可迭代对象,字符串等进行对比,使用递归地查找所有差异。
也可以用来校验多种文件内容的差异,如txt、json、图片等…
DeepDiff库常用来校验两个对象是否一致,并找出其中差异之处。
安装:
pip install deepdiff
二、DeepDiff模块
1.deepdiff常用操作
如果实际请求结果和预期值的json数据都一致,那么会返回{}空字典,否则会返回对比差异的结果,接口测试中我们也可以根据这个特点进行断言。
如果对比结果不同,将会给出下面对应的返回:
- type_changes:类型改变的key
- values_changed:值发生变化的key
- dictionary_item_added:字典key添加
- dictionary_item_removed:字段key删除
2.代码示例
2.1 对比txt文件
from deepdiff import DeepDiff # 对比file文件 f1, f2 = open('./data/a.txt', 'r', encoding='utf-8').read(), open('./data/b.txt', 'r', encoding='utf-8').read() print(DeepDiff(f1, f2))
输出:
{'values_changed': {'root': {'new_value': 'abcd', 'old_value': 'abc'}}}
2.2 对比json
from deepdiff import DeepDiff # 对比json文件 json1={ 'code': 0, "message": "成功", "data": { "total": 28, "id":123 } } json2={ 'code':0, "message":"成功", "data": { "total": 29, } } print(DeepDiff(json1, json2))
# 输出结果,id移除,total值发生改变
{'dictionary_item_removed': [root['data']['id']], 'values_changed': {"root['data']['total']": {'new_value': 29, 'old_value': 28}}}
2.3 DeepDiff在Pytest框架中的应用
注意,对比的报文必须是字典格式!!!
from deepdiff import DeepDiff import pytest import requests # DeepDiff在Pytest框架中的应用, 注意,对比的报文必须是字典格式!!! class TestCase: expect = { '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' } } def setup(self): # 返回字典格式报文 self.response = requests.get('http://www.httpbin.org.json').json() def test_case_01(self): print('用例对比结果:') print(DeepDiff(self.response, self.expect)) def test_case_02(self): print('用例对比结果:') print(DeepDiff(self.response['slideshow']['author'], 'Yours Truly1')) if __name__ == '__main__': pytest.main(['-s'])
输出:
PASSED [ 50%]用例对比结果:
{}
PASSED [100%]用例对比结果:
{'values_changed': {'root': {'new_value': 'Yours Truly1', 'old_value': 'Yours Truly'}}}
其实,在实际接口断言中,可能需要校验的字段顺序不一样,又或者有一些字段值不需要,为了解决这类问题,Deepdiff也提供了相信的参数,只需要在比较的时候加入,传入对应参数即可。
- ignore order(忽略排序)
- ignore string case(忽略大小写)
- exclude_paths排除指定的字段
代码中 使用:
print(DeepDiff(self.response, self.expect,view='tree',ignore_order=True,ignore_string_case=True,exclude_paths= {"root['slideshow']['date']"}))
3.参数介绍
3.1 展示差异的深度
cutoff_distance_for_pairs: (1 >= float > 0,默认值=0.3)
此参数通常结合ignore_order=true使用,用于结果中展示差异的深度。
值越大,则结果中展示的差异深度越大。
from deepdiff import DeepDiff t1 = [[[1.0]]] t2 = [[[20.0]]] print(DeepDiff(t1, t2, ignore_order=True, cutoff_distance_for_pairs=0.3)) print(DeepDiff(t1, t2, ignore_order=True, cutoff_distance_for_pairs=0.2)) print(DeepDiff(t1, t2, ignore_order=True, cutoff_distance_for_pairs=0.1))
输出:
{'values_changed': {'root[0][0][0]': {'new_value': 20.0, 'old_value': 1.0}}}
{'values_changed': {'root[0][0]': {'new_value': [20.0], 'old_value': [1.0]}}}
{'values_changed': {'root[0]': {'new_value': [[20.0]], 'old_value': [[1.0]]}}}
3.2 ignore types
1)ignore_string_type_changes
默认=False,默认忽略字符串类型的更改。如果ignore_string_type_changes=True,则b"Hello" 与 “Hello”被认为是相同的。
print(DeepDiff(b'hello', 'hello')) print(DeepDiff(b'hello', 'hello', ignore_string_type_changes=True))
输出:
{'type_changes': {'root': {'old_type': <class 'bytes'>, 'new_type': <class 'str'>, 'old_value': b'hello', 'new_value': 'hello'}}}
{}
2)ignore_numeric_type_changes
默认=False,表示忽略数值类型更改。设置为true时,则认为10和10.0是相同的
PS:此参数仅作用与numbers对象比较,如果拿numers和string比较则不生效
from deepdiff import DeepDiff from decimal import Decimal t1 = Decimal('10.01') t2 = 10.01 print(DeepDiff(t1, t2)) print(DeepDiff(t1, t2, ignore_numeric_type_changes=True))
输出:
{'type_changes': {'root': {'old_type': <class 'decimal.Decimal'>, 'new_type': <class 'float'>, 'old_value': Decimal('10.01'), 'new_value': 10.01}}}
{}
3.3 view
DeepDiff支持对比结果选择text视图和tree视图展示。
主要区别在于, tree视图具有遍历对象的功能,可以看到哪些对象与哪些其他对象进行了比较。
虽然视图选项决定了输出的格式,但无论你选择哪种视图,你都可以通过使用pretty()方法得到一个更适合阅读的输出
t1= {"name": "yanan", "pro": {"sh": "shandong", "city": ["zibo", "weifang"]}} t2 = {"name": "changsha", "pro": {"sh": "shandong", "town": ["taian", "weifang"]}} ddiff = DeepDiff(t1, t2, view='tree') print(ddiff) # 默认为text ddiff = DeepDiff(t1, t2, view='text') print(ddiff)
输出:
{'dictionary_item_added': [<root['pro']['town'] t1:not present, t2:['taian', 'w...]>], 'dictionary_item_removed': [<root['pro']['city'] t1:['zibo', 'we...], t2:not present>], 'values_changed': [<root['name'] t1:'yanan', t2:'changsha'>]}
{'dictionary_item_added': [root['pro']['town']], 'dictionary_item_removed': [root['pro']['city']], 'values_changed': {"root['name']": {'new_value': 'changsha', 'old_value': 'yanan'}}}
3.4 pretty( ) 方法
使用pretty( ) 方法获得更可读的输出, 无论你使用什么视图来生成结果
t1= {"name": "yanan", "pro": {"sh": "shandong", "city": ["zibo", "weifang"]}} t2 = {"name": "changsha", "pro": {"sh": "shandong", "town": ["taian", "weifang"]}} print(DeepDiff(t1, t2, view='tree').pretty()) print(DeepDiff(t1, t2, view='text').pretty())
输出:
Item root['pro']['town'] added to dictionary.
Item root['pro']['city'] removed from dictionary.
Value of root['name'] changed from "yanan" to "changsha".
Item root['pro']['town'] added to dictionary.
Item root['pro']['city'] removed from dictionary.
Value of root['name'] changed from "yanan" to "changsha".
大家在比较两个数据对象的时候,更多遇到的场景是:key值不同、key新增、key减少、key值类型改变、结构不同
t1 = { 'Author': '河马', 'wechat': 'ZZ666' } t2 = { 'Author': '河马', 'wechat': 'ZZ666', 'Blog' : 'https://www.hctestedu.com/' } t3 = { 'Author': '河马', 'wechat': 'ZZ777' } t4 = { 'Author': '河马', 'wechat': 777 } t5 = [{ 'Author': '河马', 'wechat': 'ZZ666' }] # Key值不同 print(DeepDiff(t1, t3).pretty()) # Key新增 print(DeepDiff(t1, t2).pretty()) # Key减少 print(DeepDiff(t2, t1).pretty()) # Key值类型改变 print(DeepDiff(t1, t4).pretty()) # 结构不同 print(DeepDiff(t1, t5).pretty()) # Key值相同 result = DeepDiff(t1, t1).pretty() print(DeepDiff(t1, t1).pretty()) assert "" == result
输出:
Value of root['wechat'] changed from "ZZ666" to "ZZ777".
Item root['Blog'] added to dictionary.
Item root['Blog'] removed from dictionary.
Type of root['wechat'] changed from str to int and value changed from "ZZ666" to 777.
Type of root changed from dict to list and value changed from {'Author': '展昭', 'wechat': 'ZZ666'} to [{'Author': '展昭', 'wechat': 'ZZ666'}].
三、DeepSearch模块
该模支持在对象中搜索对象。
几个重要的参数:
- use_regexp: 使用正则表达式,默认False。
- strict_checking:强校验,默认Ture。为True时,它将检查要匹配的对象的类型,因此在搜索 '1234' 时,它将不匹配 int 1234。
- case_sensitive:为True时,表示大小写敏感。
from deepdiff import DeepSearch obj = ["long somewhere", "string", 0, "somewhere great!"] # 使用正则表达式 item = "some*" ds = DeepSearch(obj, item, use_regexp=True) print(ds) # 大小写敏感 item = 'someWhere' ds = DeepSearch(obj, item, case_sensitive=True) print(ds) item = 'some' ds = DeepSearch(obj, item, case_sensitive=True) print(ds) # 强校验 item = 0 ds = DeepSearch(obj, item, strict_checking=True) print(ds) item = "0" ds = DeepSearch(obj, item, strict_checking=True) print(ds)
输出:
{'matched_values': ['root[0]', 'root[3]']}
{}
{'matched_values': ['root[0]', 'root[3]']}
{'matched_values': ['root[2]']}
{}
正则表达式这个点的应用场景比较多,当你事先对预期结果的值不能进行100%确定时,可以使用正则匹配实际值进行断言
四、grep模块
grep是DeepSearch提供的一个更好用的方法。
它所接受的参数与DeepSearch完全相同,只是需要你用管道将对象送入它,而不是将它作为参数传递。
它的工作原理和 linux shell中的grep一样
from deepdiff import grep obj = ["long somewhere", "string", 0, "somewhere great!"] item = "somewhere" ds = obj | grep(item) print(ds)
输出:
{'matched_values': ['root[0]', 'root[3]']}
五、Extract模块
该模块可以根据值抽取其Key的路径;反过来根据Key路径提取其值
from deepdiff import extract obj = {"a": [{'2': 'b'}, 3], "b": [4, 5]} # root+键名+list下标+键名 path = "root[a][0]['2']" print(extract(obj, path))
输出:
b
到此这篇关于Python全字段断言之DeepDiff模块详解的文章就介绍到这了,更多相关Python的DeepDiff模块内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!