Python: Compare nested data structures in unittest

Viewed 1991

Is there a way to compare nested data structures in unittest but ignoring concrete types of objects, like assertSequenceEqual but recursive, for example:

#!/usr/bin/env python

import unittest

class DeepCompareTestCase(unittest.TestCase):

    def test_compare(self):
        # this test fails
        self.assertSequenceEqual(
            [['abc', 'def']],
            (('abc', 'def'),)
        )

unittest.main()

(something like Test::Deep in Perl)

3 Answers

I had a similar problem to yours, but in my case I was testing the data structures delivered to an API endpoint. What I ended up doing was comparing a standardised JSON serialisation of each of the values. It's not a general solution since it will throw an exception on any value that can't be serialised to JSON, but in my case that's a feature rather than a bug and it does work in your example case, so I thought I'd share.

I created a tests/assertions.py file containing this code:

import json

class ApiAssertionsMixin(object):

    def assertJsonEqual(self, first, second, msg=None):
        j1 = json.dumps(first, sort_keys=True, indent=4)
        j2 = json.dumps(second, sort_keys=True, indent=4)
        self.maxDiff = None
        self.assertEqual(j1, j2, msg)

In your example, you'd use it like this:

import unittest
from tests.assertions import ApiAssertionsMixin

class DeepCompareTestCase(ApiAssertionsMixin, unittest.TestCase):

    def test_compare(self):
        self.assertJsonEqual(
            [['abc', 'def']],
            (('abc', 'def'),)
        )

unittest.main()

Which should pass. Here's an example of a failing test:

    def test_deep_compare(self):
        self.assertJsonEqual(
            { 'name': 'Bob', 'aliases': ['Kate', 'Robbie'], 'age': 19 },
            { 'name': 'Bob', 'age': 20, 'aliases': ['Robbie'] }
        )

With output like this:

.F
======================================================================
FAIL: test_deep_compare (__main__.DeepCompareTestCase)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "tests/test_nested.py", line 18, in test_deep_compare
    { 'name': 'Bob', 'age': 20, 'aliases': ['Robbie'] }
  File "./tests/assertions.py", line 10, in assertJsonEqual
    self.assertEqual(j1, j2, msg)
AssertionError: '{\n    "age": 19,\n    "aliases": [\n        "Kate",\n [41 chars]"\n}' != '{\n    "age": 20,\n    "aliases": [\n        "Robbie"\n[24 chars]"\n}'
  {
-     "age": 19,
?            ^^
+     "age": 20,
?            ^^
      "aliases": [
-         "Kate",
          "Robbie"
      ],
      "name": "Bob"
  }

----------------------------------------------------------------------
Ran 2 tests in 0.001s

FAILED (failures=1)

As you can see, in the event of a failure, you get plenty of context to work out what went wrong.

One downside of this approach is that the output from the test is JSON rather than Python, so if you copy/paste from the output to fix a test, you'll need to translate: true => True, false => False, null => None.

Maybe deepdiff module helps, but I did't test it.

Related