Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to achieve assertDictEqual with assertSequenceEqual applied to values

I know that, when performing assertEqual on a dictionary, assertDictEqual is called. Similarly, assertEqual on a sequence will perform assertSequenceEqual.

However, when assertDictEqual is comparing values, it appears not to make use of assertEqual, and thus assertSequenceEqual is not called.

Consider the following simple dictionaries:

lst1 = [1, 2]
lst2 = [2, 1]

d1 = {'key': lst1}
d2 = {'key': lst2}

self.assertEqual(lst1, lst2) # True
self.assertEqual(d1, d2) # False ><

How can I test dictionaries such as d1 and d2 such that their equality is properly compared, by recursively applying assertEqual-like semantics to values?

I want to avoid using external modules (as suggested in this question) if at all possible, unless they are native django extensions.


EDIT

Essentially, what I am after is a built in version of this:

def assertDictEqualUnorderedValues(self, d1, d2):
    for k,v1 in d1.iteritems():
        if k not in d2:
            self.fail('Key %s missing in %s'%(k, d2))

        v2 = d2[k]

        if isinstance(v1, Collections.iterable) and not isinstance(v1, basestring):
            self.assertValuesEqual(v1, v2)
        else:
            self.assertEqual(v1, v2)

The problem with the above code is that the error messages are not as nice as the builtin asserts, and there's probably edge cases I've ignored (as I just wrote that off the top of my head).

like image 543
sapi Avatar asked Aug 27 '13 11:08

sapi


People also ask

What is Assertdictequal?

assertEqual() in Python is a unittest library function that is used in unit testing to check the equality of two values. This function will take three parameters as input and return a boolean value depending upon the assert condition. If both input values are equal assertEqual() will return true else return false.

How do you run a test case in python?

From the context menu, select the corresponding run command. If the directory contains tests that belong to the different testing frameworks, select the configuration to be used. For example, select Run 'All tests in: <directory name>' Run pytest in <directory name>'.

Which item in Python will stop a unit test abruptly?

An exception object is created when a Python script raises an exception. If the script explicitly doesn't handle the exception, the program will be forced to terminate abruptly.


Video Answer


3 Answers

Rather than overriding assertDictEqual, why don't you recursively sort your dicts first?

def deep_sort(obj):
    """
    Recursively sort list or dict nested lists
    """

    if isinstance(obj, dict):
        _sorted = {}
        for key in sorted(obj):
            _sorted[key] = deep_sort(obj[key])

    elif isinstance(obj, list):
        new_list = []
        for val in obj:
            new_list.append(deep_sort(val))
        _sorted = sorted(new_list)

    else:
        _sorted = obj

    return _sorted

Then sort, and use normal assertDictEqual:

    dict1 = deep_sort(dict1)
    dict2 = deep_sort(dict2)

    self.assertDictEqual(dict1, dict2)

This approach has the benefit of not caring about how many levels deep your lists are.

like image 85
Chris Villa Avatar answered Oct 19 '22 20:10

Chris Villa


The TestCase.assertEqual() method calls the class' assertDictEqual() for dicts, so just override that in your subclass derivation. If you only use other assertXXX methods in the method, the error messages should be almost as nice as the built-in asserts -- but if not you can provide a msg keyword argument when you call them to control what is displayed.

import collections
import unittest

class TestSOquestion(unittest.TestCase):

    def setUp(self):
        pass # whatever...

    def assertDictEqual(self, d1, d2, msg=None): # assertEqual uses for dicts
        for k,v1 in d1.iteritems():
            self.assertIn(k, d2, msg)
            v2 = d2[k]
            if(isinstance(v1, collections.Iterable) and
               not isinstance(v1, basestring)):
                self.assertItemsEqual(v1, v2, msg)
            else:
                self.assertEqual(v1, v2, msg)
        return True

    def test_stuff(self):
        lst1 = [1, 2]
        lst2 = [2, 1]

        d1 = {'key': lst1}
        d2 = {'key': lst2}

        self.assertItemsEqual(lst1, lst2) # True
        self.assertEqual(d1, d2) # True

if __name__ == '__main__':
    unittest.main()

Output:

> python unittest_test.py
.
---------------------------------------------------------------------->
Ran 1 test in 0.000s

OK

>
like image 23
martineau Avatar answered Oct 19 '22 18:10

martineau


I had the same problem, i had to test if the fields of a model where correct. And MyModel._meta.get_all_field_names() sometimes returns ['a','b'] and sometimes ['b','a'].

When i run:

self.assertEqual(MyModel._meta.get_all_field_names(), ['a', 'b'])

it sometimes fails.

I solved it by putting both values in a set():

self.assertEqual(set(MyModel._meta.get_all_field_names()), set(['a', 'b'])) #true

self.assertEqual(set(MyModel._meta.get_all_field_names()), set(['b', 'a'])) #true

This will not work (returns True) with:

self.assertEqual(set(['a','a','b','a']), set(['a','b']))  # Also true 

But since i'm checking for field names of a model , and those are unique, this is good by me.

like image 43
Robin van Leeuwen Avatar answered Oct 19 '22 18:10

Robin van Leeuwen