FWIW, here's the wrapper function I wrote just now to do a "deep" assert over a dict
/list
.
import unittest
from collections.abc import Iterable, Mapping
from itertools import zip_longest
# thanks to https://stackoverflow.com/questions/32954486/zip-iterators-asserting-for-equal-length-in-python
def zip_equal(*iterables):
sentinel = object()
for combo in zip_longest(*iterables, fillvalue=sentinel):
if sentinel in combo:
raise ValueError('Iterables have different lengths')
yield combo
def assert_deep(instance: unittest.TestCase, method, first, second, *args, path=tuple(), **kwargs):
instance.assertIsInstance(second, type(first))
if isinstance(first, Mapping):
instance.assertEqual(first.keys(), second.keys())
for key in first:
subtest_path = path + (key,)
with instance.subTest(subtest_path):
assert_deep(instance, method, first[key], second[key], path=subtest_path, *args, **kwargs)
return instance
# thanks to https://stackoverflow.com/questions/1952464/in-python-how-do-i-determine-if-an-object-is-iterable
if isinstance(first, Iterable):
index = 0
try:
for first_el, second_el in zip_equal(first, second):
# avoid infinite recursions on iterables that return themselves as elements, e.g. strings
if first_el is first:
return method(first, second, *args, **kwargs)
subtest_path = path + (index,)
with instance.subTest(subtest_path):
assert_deep(instance, method, first_el, second_el, *args, path=subtest_path, **kwargs)
index += 1
except ValueError as e:
instance.fail(e)
return instance
return method(first, second, *args, **kwargs)
class AssertDeepTest(unittest.TestCase):
def test_assert_deep_ok(self):
assert_deep(self, self.assertAlmostEqual,
{'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]},
{'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501]},
places=2)
assert_deep(self, self.assertEqual,
{'a': [1, 1.0, 'a'], 'foo': [2, 'bar']},
{'foo': [2, 'bar'], 'a': [1, 1.0, 'a']})
@unittest.expectedFailure
def test_assert_deep_fail_method(self):
assert_deep(self, self.assertAlmostEqual,
{'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]},
{'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501]},
places=3)
@unittest.expectedFailure
def test_assert_deep_fail_keys(self):
assert_deep(self, self.assertAlmostEqual,
{'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]},
{'a': [0.101, 0.201], 'c': [0.301, 0.401, 0.501]},
places=2)
@unittest.expectedFailure
def test_assert_deep_fail_types(self):
assert_deep(self, self.assertAlmostEqual,
{'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]},
{'a': [0.101, 0.201], 'b': (0.301, 0.401, 0.501)},
places=2)
@unittest.expectedFailure
def test_assert_deep_fail_lens(self):
assert_deep(self, self.assertAlmostEqual,
{'a': [0.100, 0.200], 'b': [0.300, 0.400, 0.500]},
{'a': [0.101, 0.201], 'b': [0.301, 0.401, 0.501, 0.601]},
places=2)