Comparison of multi-line strings in Python unit test

12,833

A little digging in the Python source code shows that TestCase registers a bunch of methods to test equality for different types.

self.addTypeEqualityFunc(dict, 'assertDictEqual')
self.addTypeEqualityFunc(list, 'assertListEqual')
self.addTypeEqualityFunc(tuple, 'assertTupleEqual')
self.addTypeEqualityFunc(set, 'assertSetEqual')
self.addTypeEqualityFunc(frozenset, 'assertSetEqual')
try:
    self.addTypeEqualityFunc(unicode, 'assertMultiLineEqual')
except NameError:
    # No unicode support in this build
    pass

You can see that unicode is registered to use assertMultiLineEqual(), but str is not registered for anything special. I have no idea why str is left out, but so far I have been happy with either of the following two methods.

Call Directly

If an 8-bit string isn't registered to use assertMultiLineEqual() by default, you can still call it directly.

def testString(self):
    a = 'xax\nzzz'
    b = 'xbx\nzzz'
    self.assertMultiLineEqual(a, b)

Register String Type

You can also register it yourself. Just add an extra line to your test case's setUp() method. Do it once, and all your test methods will use the right method to test equality. If your project has a common base class for all test cases, that would be a great place to put it.

class TestAssertEqual(unittest.TestCase):
    def setUp(self):
        super(TestAssertEqual, self).setUp()
        self.addTypeEqualityFunc(str, self.assertMultiLineEqual)

    def testString(self):
        a = 'xax\nzzz'
        b = 'xbx\nzzz'
        self.assertEqual(a, b)

    def testUnicode(self):
        a = u'xax\nzzz'
        b = u'xbx\nzzz'
        self.assertEqual(a, b)

Either of these methods will include highlighting when the string comparison fails.

Share:
12,833
Don Kirkby
Author by

Don Kirkby

Python, Java, and C# developer working in AIDS research. Hobbies include designing board games and puzzles, as well as learning Chinese. If you just want to see the codez, check out GitHub. To contact me, use Twitter or e-mail [email protected] .

Updated on June 15, 2022

Comments

  • Don Kirkby
    Don Kirkby almost 2 years

    When I compare two Unicode strings in a Python unit test, it gives a nice failure message highlighting which lines and characters are different. However, comparing two 8-bit strings just shows the two strings with no highlighting.

    How can I get the highlighting for both Unicode and 8-bit strings?

    Here is an example unit test that shows both comparisons:

    import unittest
    
    class TestAssertEqual(unittest.TestCase):
        def testString(self):
            a = 'xax\nzzz'
            b = 'xbx\nzzz'
            self.assertEqual(a, b)
    
        def testUnicode(self):
            a = u'xax\nzzz'
            b = u'xbx\nzzz'
            self.assertEqual(a, b)
    
    if __name__ == '__main__':
        unittest.main()
    

    The results of this test show the difference:

    FF
    ======================================================================
    FAIL: testString (__main__.TestAssertEqual)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/mnt/data/don/workspace/scratch/scratch.py", line 7, in testString
        self.assertEqual(a, b)
    AssertionError: 'xax\nzzz' != 'xbx\nzzz'
    
    ======================================================================
    FAIL: testUnicode (__main__.TestAssertEqual)
    ----------------------------------------------------------------------
    Traceback (most recent call last):
      File "/mnt/data/don/workspace/scratch/scratch.py", line 12, in testUnicode
        self.assertEqual(a, b)
    AssertionError: u'xax\nzzz' != u'xbx\nzzz'
    - xax
    ?  ^
    + xbx
    ?  ^
      zzz
    
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    FAILED (failures=2)
    

    Update for Python 3

    In Python 3, string literals are Unicode by default, so this is mostly irrelevant. assertMultiLineEqual() no longer supports byte strings, so you're pretty much stuck with regular assertEqual() unless you're willing to decode the byte strings to Unicode.

  • 2rs2ts
    2rs2ts over 7 years
    Registering the string type doesn't work for me, but calling the method directly does.
  • Pierre.Sassoulas
    Pierre.Sassoulas over 7 years
    TLDR : Use assertMultiLineEqual for all.
  • Zephaniah Grunschlag
    Zephaniah Grunschlag almost 3 years
    Wow. Ridiculously complicated. Is there a pytest variant for this?
  • Don Kirkby
    Don Kirkby almost 3 years
    Unless you're stuck using Python 2, @ZephaniahGrunschlag, this shouldn't be an issue anymore. String literals are Unicode by default in Python 3.
  • Zephaniah Grunschlag
    Zephaniah Grunschlag almost 3 years
    Thanks @DonKirkby. My problem was that I was comparing a multiline f-string to a standard multiline string. It was adding a whole bunch of \n characters to one and not the other. Then I converted the standard string to an f-string and the comparison worked (but I had to but a lint ignore for using an f-string without any interpolated variable)
  • Don Kirkby
    Don Kirkby almost 3 years
    Sounds like you should ask a separate question, @ZephaniahGrunschlag. F-strings shouldn't change white space behaviour, as far as I know.