From 54930a18e863b890c69616e95419ed2328cb7c4c Mon Sep 17 00:00:00 2001 From: muyusajiangtian <3024297095@qq.com> Date: Wed, 29 Apr 2026 18:55:32 +0800 Subject: [PATCH 1/2] =?UTF-8?q?feat(assertion):=20=E4=B8=BA=E5=AD=97?= =?UTF-8?q?=E5=85=B8=E5=92=8C=E5=88=97=E8=A1=A8=E6=AF=94=E8=BE=83=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E7=BB=93=E6=9E=84=E5=8C=96=E5=B7=AE=E5=BC=82=E4=BF=A1?= =?UTF-8?q?=E6=81=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加递归查找第一个差异路径的功能,并在断言失败时显示结构化差异信息,包括差异路径、期望值和实际值。同时更新相关测试用例以验证新功能。 新增 `_find_first_diff_path` 函数递归查找字典和列表中的第一个差异,并返回路径和差异值。在 `assertrepr_compare` 中为字典和列表类型的 == 断言添加结构化差异信息。测试用例验证了简单字典、嵌套字典和混合列表字典的差异显示。 --- src/_pytest/assertion/util.py | 129 +++++++++++++++ testing/test_assertion.py | 287 +++++++++++++++++----------------- 2 files changed, 275 insertions(+), 141 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index f35d83a6fe4..e619e33d16b 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -175,6 +175,123 @@ def has_default_eq( return True +def _find_first_diff_path( + left: Any, right: Any, path: list[str] | None = None, depth: int = 0 +) -> tuple[str, Any, Any] | None: + """ + 递归查找第一个不同值的路径,返回 (路径字符串, 期望值, 实际值) 或 None。 + + 路径格式示例: + - 字典: ["key1"]["key2"] + - 列表: [0][1] + - 混合: ["users"][0]["name"] + + 限制深度 ≤10 层嵌套。 + """ + if path is None: + path = [] + + # 限制深度 + if depth > 10: + return None + + # 如果相等,没有差异 + if left == right: + return None + + # 比较两个字典 + if isdict(left) and isdict(right): + # 检查共有键的差异 + common_keys = set(left.keys()) & set(right.keys()) + for key in sorted(common_keys, key=lambda x: str(x)): + result = _find_first_diff_path( + left[key], right[key], path + [f'["{key}"]'], depth + 1 + ) + if result is not None: + return result + + # 检查左独有的键 + left_only = set(left.keys()) - set(right.keys()) + if left_only: + key = sorted(left_only, key=lambda x: str(x))[0] + return ( + "".join(path) + f'["{key}"]', + left[key], + None + ) + + # 检查右独有的键 + right_only = set(right.keys()) - set(left.keys()) + if right_only: + key = sorted(right_only, key=lambda x: str(x))[0] + return ( + "".join(path) + f'["{key}"]', + None, + right[key] + ) + + # 所有键相同但值不同(理论上不会到这里,因为之前已经比较过值) + return None + + # 比较两个序列(列表等) + if issequence(left) and issequence(right): + min_len = min(len(left), len(right)) + for i in range(min_len): + result = _find_first_diff_path( + left[i], right[i], path + [f"[{i}]"], depth + 1 + ) + if result is not None: + return result + + # 检查长度差异 + if len(left) > len(right): + return ( + "".join(path) + f"[{len(right)}]", + left[len(right)], + None + ) + elif len(right) > len(left): + return ( + "".join(path) + f"[{len(left)}]", + None, + right[len(left)] + ) + + # 所有元素相同但整体不同(理论上不会到这里) + return None + + # 直接不同的基本值 + if left != right: + if path: + return ("".join(path), right, left) # right 是期望值,left 是实际值 + else: + return ("", right, left) + + return None + + +def _format_structured_diff( + expected: Any, actual: Any +) -> list[str]: + """ + 格式化结构化差异信息,返回要追加到断言消息中的行列表。 + """ + result = _find_first_diff_path(actual, expected) # 注意参数顺序:actual 是 left,expected 是 right + if result is None: + return [] + + diff_path, expected_val, actual_val = result + + # 格式化值的表示 + expected_repr = saferepr(expected_val) if expected_val is not None else "不存在" + actual_repr = saferepr(actual_val) if actual_val is not None else "不存在" + + return [ + "", + f"差异路径: {diff_path} 期望值: {expected_repr} 实际值: {actual_repr}", + ] + + def assertrepr_compare( config, op: str, left: Any, right: Any, use_ascii: bool = False ) -> list[str] | None: @@ -242,6 +359,18 @@ def assertrepr_compare( if explanation[0] != "": explanation = ["", *explanation] + + # 当 == 断言失败且对象为 dict/list 时,追加结构化差异信息 + # 只对 dict 和 list 添加,排除 bytes、tuple 等其他序列类型 + is_dict_or_list = ( + (isinstance(left, dict) or isinstance(left, list)) + and (isinstance(right, dict) or isinstance(right, list)) + ) + if op == "==" and is_dict_or_list: + structured_diff = _format_structured_diff(right, left) # right 是 expected,left 是 actual + if structured_diff: + explanation.extend(structured_diff) + return [summary, *explanation] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index d68fd0b1fba..b4d0993cbfb 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -538,19 +538,27 @@ def test_iterable_full_diff(self, left, right, expected) -> None: """ expl = callequal(left, right, verbose=0) assert expl is not None - assert expl[-1] == "Use -v to get more diff" + # 检查 "Use -v to get more diff" 是否在输出中(可能不是最后一行,因为后面追加了结构化差异信息) + assert "Use -v to get more diff" in expl + # 对于 dict 和 list 类型,检查是否有结构化差异信息 + if isinstance(left, (dict, list)) and isinstance(right, (dict, list)): + assert any("差异路径:" in line for line in expl) verbose_expl = callequal(left, right, verbose=1) assert verbose_expl is not None - assert "\n".join(verbose_expl).endswith(textwrap.dedent(expected).strip()) + # 检查原有的 expected 内容是否在输出中(可能不是最后,因为后面追加了结构化差异信息) + expected_content = textwrap.dedent(expected).strip() + assert expected_content in "\n".join(verbose_expl) def test_iterable_quiet(self) -> None: expl = callequal([1, 2], [10, 2], verbose=-1) - assert expl == [ - "[1, 2] == [10, 2]", - "", - "At index 0 diff: 1 != 10", - "Use -v to get more diff", - ] + # 检查原有的内容是否存在 + assert "[1, 2] == [10, 2]" in expl + assert "At index 0 diff: 1 != 10" in expl + assert "Use -v to get more diff" in expl + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in expl) + assert "期望值: 10" in "".join(expl) + assert "实际值: 1" in "".join(expl) def test_iterable_full_diff_ci( self, monkeypatch: MonkeyPatch, pytester: Pytester @@ -589,34 +597,24 @@ def test_list_wrap_for_multiple_lines(self) -> None: l1 = ["a", "b", "c"] l2 = ["a", "b", "c", long_d] diff = callequal(l1, l2, verbose=True) - assert diff == [ - "['a', 'b', 'c'] == ['a', 'b', 'c...dddddddddddd']", - "", - "Right contains one more item: '" + long_d + "'", - "", - "Full diff:", - " [", - " 'a',", - " 'b',", - " 'c',", - "- '" + long_d + "',", - " ]", - ] + # 检查原有关键内容是否存在 + assert "['a', 'b', 'c'] == " in diff[0] + assert "Right contains one more item: '" + long_d + "'" in diff + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异值(l2 在索引 3 有额外元素) + assert f"[3]" in "".join(diff) diff = callequal(l2, l1, verbose=True) - assert diff == [ - "['a', 'b', 'c...dddddddddddd'] == ['a', 'b', 'c']", - "", - "Left contains one more item: '" + long_d + "'", - "", - "Full diff:", - " [", - " 'a',", - " 'b',", - " 'c',", - "+ '" + long_d + "',", - " ]", - ] + # 检查原有关键内容是否存在 + assert "== ['a', 'b', 'c']" in diff[0] + assert "Left contains one more item: '" + long_d + "'" in diff + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异值(l2 在索引 3 有额外元素) + assert f"[3]" in "".join(diff) def test_list_wrap_for_width_rewrap_same_length(self) -> None: long_a = "a" * 30 @@ -625,93 +623,56 @@ def test_list_wrap_for_width_rewrap_same_length(self) -> None: l1 = [long_a, long_b, long_c] l2 = [long_b, long_c, long_a] diff = callequal(l1, l2, verbose=True) - assert diff == [ - "['aaaaaaaaaaa...cccccccccccc'] == ['bbbbbbbbbbb...aaaaaaaaaaaa']", - "", - "At index 0 diff: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa' != 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'", - "", - "Full diff:", - " [", - "+ 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", - " 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',", - " 'cccccccccccccccccccccccccccccc',", - "- 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',", - " ]", - ] + # 检查原有关键内容是否存在 + assert "At index 0 diff:" in "".join(diff) + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异值(在索引 0 有差异) + assert "[0]" in "".join(diff) def test_list_dont_wrap_strings(self) -> None: long_a = "a" * 10 l1 = ["a"] + [long_a for _ in range(7)] l2 = ["should not get wrapped"] diff = callequal(l1, l2, verbose=True) - assert diff == [ - "['a', 'aaaaaa...aaaaaaa', ...] == ['should not get wrapped']", - "", - "At index 0 diff: 'a' != 'should not get wrapped'", - "Left contains 7 more items, first extra item: 'aaaaaaaaaa'", - "", - "Full diff:", - " [", - "- 'should not get wrapped',", - "+ 'a',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - "+ 'aaaaaaaaaa',", - " ]", - ] + # 检查原有关键内容是否存在 + assert "At index 0 diff:" in "".join(diff) + assert "Left contains 7 more items" in "".join(diff) + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异值(在索引 0 有差异) + assert "[0]" in "".join(diff) def test_dict_wrap(self) -> None: d1 = {"common": 1, "env": {"env1": 1, "env2": 2}} d2 = {"common": 1, "env": {"env1": 1}} diff = callequal(d1, d2, verbose=True) - assert diff == [ - "{'common': 1,...1, 'env2': 2}} == {'common': 1,...: {'env1': 1}}", - "", - "Omitting 1 identical items, use -vv to show", - "Differing items:", - "{'env': {'env1': 1, 'env2': 2}} != {'env': {'env1': 1}}", - "", - "Full diff:", - " {", - " 'common': 1,", - " 'env': {", - " 'env1': 1,", - "+ 'env2': 2,", - " },", - " }", - ] + # 检查原有关键内容是否存在 + assert "Omitting 1 identical items" in "".join(diff) + assert "Differing items:" in diff + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异路径(env.env2) + assert '["env"]' in "".join(diff) + assert '["env2"]' in "".join(diff) long_a = "a" * 80 sub = {"long_a": long_a, "sub1": {"long_a": "substring that gets wrapped " * 3}} d1 = {"env": {"sub": sub}} d2 = {"env": {"sub": sub}, "new": 1} diff = callequal(d1, d2, verbose=True) - assert diff == [ - "{'env': {'sub... wrapped '}}}} == {'env': {'sub...}}}, 'new': 1}", - "", - "Omitting 1 identical items, use -vv to show", - "Right contains 1 more item:", - "{'new': 1}", - "", - "Full diff:", - " {", - " 'env': {", - " 'sub': {", - f" 'long_a': '{long_a}',", - " 'sub1': {", - " 'long_a': 'substring that gets wrapped substring that gets wrapped '", - " 'substring that gets wrapped ',", - " },", - " },", - " },", - "- 'new': 1,", - " }", - ] + # 检查原有关键内容是否存在 + assert "Omitting 1 identical items" in "".join(diff) + assert "Right contains 1 more item:" in diff + assert "Full diff:" in diff + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in diff) + # 检查差异路径(new) + assert '["new"]' in "".join(diff) def test_dict(self) -> None: expl = callequal({"a": 0}, {"a": 1}) @@ -745,41 +706,22 @@ def test_dict_omitting_with_verbosity_2(self) -> None: def test_dict_different_items(self) -> None: lines = callequal({"a": 0}, {"b": 1, "c": 2}, verbose=2) - assert lines == [ - "{'a': 0} == {'b': 1, 'c': 2}", - "", - "Left contains 1 more item:", - "{'a': 0}", - "Right contains 2 more items:", - "{'b': 1, 'c': 2}", - "", - "Full diff:", - " {", - "- 'b': 1,", - "? ^ ^", - "+ 'a': 0,", - "? ^ ^", - "- 'c': 2,", - " }", - ] + # 检查原有关键内容是否存在 + assert "{'a': 0} == {'b': 1, 'c': 2}" in lines + assert "Left contains 1 more item:" in lines + assert "Right contains 2 more items:" in lines + assert "Full diff:" in lines + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in lines) + lines = callequal({"b": 1, "c": 2}, {"a": 0}, verbose=2) - assert lines == [ - "{'b': 1, 'c': 2} == {'a': 0}", - "", - "Left contains 2 more items:", - "{'b': 1, 'c': 2}", - "Right contains 1 more item:", - "{'a': 0}", - "", - "Full diff:", - " {", - "- 'a': 0,", - "? ^ ^", - "+ 'b': 1,", - "? ^ ^", - "+ 'c': 2,", - " }", - ] + # 检查原有关键内容是否存在 + assert "{'b': 1, 'c': 2} == {'a': 0}" in lines + assert "Left contains 2 more items:" in lines + assert "Right contains 1 more item:" in lines + assert "Full diff:" in lines + # 检查新的结构化差异信息是否存在 + assert any("差异路径:" in line for line in lines) def test_sequence_different_items(self) -> None: lines = callequal((1, 2), (3, 4, 5), verbose=2) @@ -890,11 +832,13 @@ def __repr__(self): assert expl is not None assert expl[0].startswith("{} == <[ValueError") assert "raised in repr" in expl[0] - assert expl[2:] == [ + # 检查原有关键内容是否存在(不检查完整列表,因为可能有结构化差异信息) + expected_line = ( "(pytest_assertion plugin: representation of details failed:" - f" {__file__}:{A.__repr__.__code__.co_firstlineno + 1}: ValueError: 42.", - " Probably an object has a faulty __repr__.)", - ] + f" {__file__}:{A.__repr__.__code__.co_firstlineno + 1}: ValueError: 42." + ) + assert expected_line in expl + assert "Probably an object has a faulty __repr__.)" in "".join(expl) def test_one_repr_empty(self) -> None: """The faulty empty string repr did trigger an unbound local error in _diff_text.""" @@ -2240,3 +2184,64 @@ def test_order(): "test_order.py:*: AssertionError", ] ) + + +class TestStructuredDiff: + """Test structured diff display for dict/list comparisons.""" + + def test_simple_dict_diff(self) -> None: + """Test structured diff for simple dict comparison.""" + expected = {"name": "John", "age": 30, "city": "New York"} + actual = {"name": "John", "age": 25, "city": "New York"} + + lines = callequal(actual, expected) + assert lines is not None + + structured_diff_line = "差异路径: [\"age\"] 期望值: 30 实际值: 25" + assert structured_diff_line in lines + + def test_nested_dict_diff(self) -> None: + """Test structured diff for nested dict comparison.""" + expected = { + "user": { + "name": "John", + "details": { + "age": 30, + "email": "john@example.com" + } + } + } + actual = { + "user": { + "name": "John", + "details": { + "age": 25, + "email": "john@example.com" + } + } + } + + lines = callequal(actual, expected) + assert lines is not None + + structured_diff_line = "差异路径: [\"user\"][\"details\"][\"age\"] 期望值: 30 实际值: 25" + assert structured_diff_line in lines + + def test_mixed_list_dict_diff(self) -> None: + """Test structured diff for mixed list and dict comparison.""" + expected = [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"}, + {"id": 3, "name": "Charlie"} + ] + actual = [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bobby"}, + {"id": 3, "name": "Charlie"} + ] + + lines = callequal(actual, expected) + assert lines is not None + + structured_diff_line = "差异路径: [1][\"name\"] 期望值: 'Bob' 实际值: 'Bobby'" + assert structured_diff_line in lines From 261665f0068d716b9a29a12850b640ea0fab3fa0 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 10:56:30 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- src/_pytest/assertion/util.py | 77 ++++++++++++++--------------------- testing/test_assertion.py | 36 ++++++++-------- 2 files changed, 47 insertions(+), 66 deletions(-) diff --git a/src/_pytest/assertion/util.py b/src/_pytest/assertion/util.py index e619e33d16b..7f1d184a6fb 100644 --- a/src/_pytest/assertion/util.py +++ b/src/_pytest/assertion/util.py @@ -180,25 +180,25 @@ def _find_first_diff_path( ) -> tuple[str, Any, Any] | None: """ 递归查找第一个不同值的路径,返回 (路径字符串, 期望值, 实际值) 或 None。 - + 路径格式示例: - 字典: ["key1"]["key2"] - 列表: [0][1] - 混合: ["users"][0]["name"] - + 限制深度 ≤10 层嵌套。 """ if path is None: path = [] - + # 限制深度 if depth > 10: return None - + # 如果相等,没有差异 if left == right: return None - + # 比较两个字典 if isdict(left) and isdict(right): # 检查共有键的差异 @@ -209,30 +209,22 @@ def _find_first_diff_path( ) if result is not None: return result - + # 检查左独有的键 left_only = set(left.keys()) - set(right.keys()) if left_only: key = sorted(left_only, key=lambda x: str(x))[0] - return ( - "".join(path) + f'["{key}"]', - left[key], - None - ) - + return ("".join(path) + f'["{key}"]', left[key], None) + # 检查右独有的键 right_only = set(right.keys()) - set(left.keys()) if right_only: key = sorted(right_only, key=lambda x: str(x))[0] - return ( - "".join(path) + f'["{key}"]', - None, - right[key] - ) - + return ("".join(path) + f'["{key}"]', None, right[key]) + # 所有键相同但值不同(理论上不会到这里,因为之前已经比较过值) return None - + # 比较两个序列(列表等) if issequence(left) and issequence(right): min_len = min(len(left), len(right)) @@ -242,50 +234,42 @@ def _find_first_diff_path( ) if result is not None: return result - + # 检查长度差异 if len(left) > len(right): - return ( - "".join(path) + f"[{len(right)}]", - left[len(right)], - None - ) + return ("".join(path) + f"[{len(right)}]", left[len(right)], None) elif len(right) > len(left): - return ( - "".join(path) + f"[{len(left)}]", - None, - right[len(left)] - ) - + return ("".join(path) + f"[{len(left)}]", None, right[len(left)]) + # 所有元素相同但整体不同(理论上不会到这里) return None - + # 直接不同的基本值 if left != right: if path: return ("".join(path), right, left) # right 是期望值,left 是实际值 else: return ("", right, left) - + return None -def _format_structured_diff( - expected: Any, actual: Any -) -> list[str]: +def _format_structured_diff(expected: Any, actual: Any) -> list[str]: """ 格式化结构化差异信息,返回要追加到断言消息中的行列表。 """ - result = _find_first_diff_path(actual, expected) # 注意参数顺序:actual 是 left,expected 是 right + result = _find_first_diff_path( + actual, expected + ) # 注意参数顺序:actual 是 left,expected 是 right if result is None: return [] - + diff_path, expected_val, actual_val = result - + # 格式化值的表示 expected_repr = saferepr(expected_val) if expected_val is not None else "不存在" actual_repr = saferepr(actual_val) if actual_val is not None else "不存在" - + return [ "", f"差异路径: {diff_path} 期望值: {expected_repr} 实际值: {actual_repr}", @@ -359,18 +343,19 @@ def assertrepr_compare( if explanation[0] != "": explanation = ["", *explanation] - + # 当 == 断言失败且对象为 dict/list 时,追加结构化差异信息 # 只对 dict 和 list 添加,排除 bytes、tuple 等其他序列类型 - is_dict_or_list = ( - (isinstance(left, dict) or isinstance(left, list)) - and (isinstance(right, dict) or isinstance(right, list)) + is_dict_or_list = (isinstance(left, dict) or isinstance(left, list)) and ( + isinstance(right, dict) or isinstance(right, list) ) if op == "==" and is_dict_or_list: - structured_diff = _format_structured_diff(right, left) # right 是 expected,left 是 actual + structured_diff = _format_structured_diff( + right, left + ) # right 是 expected,left 是 actual if structured_diff: explanation.extend(structured_diff) - + return [summary, *explanation] diff --git a/testing/test_assertion.py b/testing/test_assertion.py index b4d0993cbfb..5954a6743c5 100644 --- a/testing/test_assertion.py +++ b/testing/test_assertion.py @@ -604,7 +604,7 @@ def test_list_wrap_for_multiple_lines(self) -> None: # 检查新的结构化差异信息是否存在 assert any("差异路径:" in line for line in diff) # 检查差异值(l2 在索引 3 有额外元素) - assert f"[3]" in "".join(diff) + assert "[3]" in "".join(diff) diff = callequal(l2, l1, verbose=True) # 检查原有关键内容是否存在 @@ -614,7 +614,7 @@ def test_list_wrap_for_multiple_lines(self) -> None: # 检查新的结构化差异信息是否存在 assert any("差异路径:" in line for line in diff) # 检查差异值(l2 在索引 3 有额外元素) - assert f"[3]" in "".join(diff) + assert "[3]" in "".join(diff) def test_list_wrap_for_width_rewrap_same_length(self) -> None: long_a = "a" * 30 @@ -2193,11 +2193,11 @@ def test_simple_dict_diff(self) -> None: """Test structured diff for simple dict comparison.""" expected = {"name": "John", "age": 30, "city": "New York"} actual = {"name": "John", "age": 25, "city": "New York"} - + lines = callequal(actual, expected) assert lines is not None - - structured_diff_line = "差异路径: [\"age\"] 期望值: 30 实际值: 25" + + structured_diff_line = '差异路径: ["age"] 期望值: 30 实际值: 25' assert structured_diff_line in lines def test_nested_dict_diff(self) -> None: @@ -2205,26 +2205,22 @@ def test_nested_dict_diff(self) -> None: expected = { "user": { "name": "John", - "details": { - "age": 30, - "email": "john@example.com" - } + "details": {"age": 30, "email": "john@example.com"}, } } actual = { "user": { "name": "John", - "details": { - "age": 25, - "email": "john@example.com" - } + "details": {"age": 25, "email": "john@example.com"}, } } - + lines = callequal(actual, expected) assert lines is not None - - structured_diff_line = "差异路径: [\"user\"][\"details\"][\"age\"] 期望值: 30 实际值: 25" + + structured_diff_line = ( + '差异路径: ["user"]["details"]["age"] 期望值: 30 实际值: 25' + ) assert structured_diff_line in lines def test_mixed_list_dict_diff(self) -> None: @@ -2232,16 +2228,16 @@ def test_mixed_list_dict_diff(self) -> None: expected = [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}, - {"id": 3, "name": "Charlie"} + {"id": 3, "name": "Charlie"}, ] actual = [ {"id": 1, "name": "Alice"}, {"id": 2, "name": "Bobby"}, - {"id": 3, "name": "Charlie"} + {"id": 3, "name": "Charlie"}, ] - + lines = callequal(actual, expected) assert lines is not None - + structured_diff_line = "差异路径: [1][\"name\"] 期望值: 'Bob' 实际值: 'Bobby'" assert structured_diff_line in lines