Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Comparison of multi-line strings in Python unit test

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.

like image 964
Don Kirkby Avatar asked Sep 02 '15 17:09

Don Kirkby


1 Answers

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.

like image 200
Don Kirkby Avatar answered Sep 21 '22 06:09

Don Kirkby