A better unittest

Duncan Booth duncan at NOSPAMrcp.co.uk
Thu Apr 17 06:13:51 EDT 2003


Thomas Heller <theller at python.net> wrote in news:d6jmqr2q.fsf at python.net:
> "Terry Reedy" <tjreedy at udel.edu> writes:
>> Hmmm.  What I would really like is a structure comparison function
>> that spit out the first difference found  so I did not have to print
>> each and compare by eye.  That is not directly in the scope of your
>> problem, but it would be the most useful succinct display.
> 
> As you say, it's a different problem.
> The patches simply rearrange the output.
> 

Anyone fancy a patch that gives the output below? (I do hope it doesn't word wrap).

FFFF
======================================================================
FAIL: test_failUnlessEqual (__main__.Test)
----------------------------------------------------------------------
TestFailed: failUnlessEqual
'xxxxxxxxxxxxxxxxxx...xxxxyzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz...
                   ...    ^
'xxxxxxxxxxxxxxxxxx...xxxxazzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz...
                   ...    ^
  File "J:\temp\test.py", line 7, in test_failUnlessEqual
    self.failUnlessEqual(s, t)

======================================================================
FAIL: test_failunlessEqual2 (__main__.Test)
----------------------------------------------------------------------
TestFailed: <object object at 0x007E63B8> != <object object at 0x007E63C0>
  File "J:\temp\test.py", line 10, in test_failunlessEqual2
    self.failUnlessEqual(object(), object())

======================================================================
FAIL: test_failunlessEqual3 (__main__.Test)
----------------------------------------------------------------------
TestFailed: failUnlessEqual
{'A': 65, 'C': 67, ...0, 'S': 83, 'R': 82, 'U': 85, 'T': 84, 'W': 87, 'V': 8...
                   ...    ^^^^^^
{'A': 65, 'C': 67, ...0, 'P ': 0, 'R': 82, 'U': 85, 'T': 84, 'W': 87, 'V': 8...
                   ...    ^^^^^^                                            ...
  File "J:\temp\test.py", line 18, in test_failunlessEqual3
    self.failUnlessEqual(d1, d2)

======================================================================
FAIL: test_failunlessEqual4 (__main__.Test)
----------------------------------------------------------------------
TestFailed: failUnlessEqual
{'A': 65, 'C': 67, ...0, 'P ': 0, 'R': 82, 'U': 85, 'T': 84, 'W': 87, 'V': 8...
                   ...    ^^^^^^                                            ...
{'A': 65, 'C': 67, ...0, 'S': 83, 'R': 82, 'U': 85, 'T': 84, 'W': 87, 'V': 8...
                   ...    ^^^^^^
  File "J:\temp\test.py", line 26, in test_failunlessEqual4
    self.failUnlessEqual(d2, d1)

----------------------------------------------------------------------
Ran 4 tests in 0.187s

FAILED (failures=4)

The patch follows (it assumes you already applied Thomas Heller's patch):

----- unittest.diff -----
*** unittest.py.orig	Thu Apr 17 10:08:03 2003
--- unittest.py	Thu Apr 17 11:07:19 2003
***************
*** 146,152 ****
  
  class TestFailed(Exception):
      pass
!   
  class TestCase:
      """A class whose instances are single test cases.
  
--- 146,199 ----
  
  class TestFailed(Exception):
      pass
! 
! def shortdiff(x,y):
!     '''shortdiff(x,y)
! 
!     Compare strings x and y and display differences.
!     If the strings are too long, shorten them to fit
!     in one line, while still keeping at least some difference.
!     '''
!     import difflib
!     LINELEN = 79
!     def limit(s):
!         if len(s) > LINELEN:
!             return s[:LINELEN-3] + '...'
!         return s
! 
!     d = difflib.Differ()
!     diffs = list(d._fancy_replace([x], 0, 1, [y], 0, 1))
!     if len(diffs) == 3:
!         if diffs[1].startswith('+ '):
!             diffs.insert(1, '')
!         else:
!             diffs.append('')
!     if len(diffs) != 4:
!         # String were the same!
!         #return '\n'.join(diffs)
!         return limit(diffs[0][2:])
! 
!     # Remove '- ', '+ ', '? ' markers from start of lines,
!     # and newlines from end.
!     diffs = [ s[2:].rstrip() for s in diffs]
!     if max([len(s) for s in diffs]) < LINELEN:
!         return '\n'.join(diffs)
! 
!     # Need to shorten the string, but make sure
!     # first difference is visible.
!     diff1, diff2 = diffs[1], diffs[3]
!     a = len(diff1) - len(diff1.lstrip())
!     b = len(diff2) - len(diff2.lstrip())
!     if a == 0 or (b != 0 and b < a):
!         a = b
! 
!     if a > LINELEN/2:
!         diffs = [ s[:LINELEN/4] + '...' + s[a - 4:]
!                   for s in diffs ]
! 
!     diffs = [ limit(s) for s in diffs ]
!     return '\n'.join(diffs)
! 
  class TestCase:
      """A class whose instances are single test cases.
  
***************
*** 317,324 ****
             operator.
          """
          if first != second:
              raise self.failureException, \
!                   (msg or '%s != %s' % (`first`, `second`))
  
      def failIfEqual(self, first, second, msg=None):
          """Fail if the two objects are equal as determined by the '=='
--- 364,374 ----
             operator.
          """
          if first != second:
+             reprfirst, reprsecond = repr(first), repr(second)
+             if not msg and len(reprfirst) + len(reprsecond) > 60:
+                 msg = "failUnlessEqual\n" + shortdiff(reprfirst, reprsecond)
              raise self.failureException, \
!                   (msg or '%s != %s' % (reprfirst, reprsecond))
  
      def failIfEqual(self, first, second, msg=None):
          """Fail if the two objects are equal as determined by the '=='
-------------------------

For completeness test.py with the failing tests
---- test.py ----
import unittest

class Test(unittest.TestCase):
    def test_failUnlessEqual(self):
        s = 5000*'x'+'y'+5000*'z'
        t = 5000*'x'+'a'+5000*'z'
        self.failUnlessEqual(s, t)

    def test_failunlessEqual2(self):
        self.failUnlessEqual(object(), object())

    def test_failunlessEqual3(self):
        d1 = {}
        for i in range(65, 96):
            d1[chr(i)] = i
        d2 = {'P ': 0}
        d2.update(d1)
        self.failUnlessEqual(d1, d2)

    def test_failunlessEqual4(self):
        d1 = {}
        for i in range(65, 96):
            d1[chr(i)] = i
        d2 = {'P ': 0}
        d2.update(d1)
        self.failUnlessEqual(d2, d1)
        
if __name__=='__main__':
    try:
        unittest.main()
    except SystemExit:
        pass

-----------------

-- 
Duncan Booth                                             duncan at rcp.co.uk
int month(char *p){return(124864/((p[0]+p[1]-p[2]&0x1f)+1)%12)["\5\x8\3"
"\6\7\xb\1\x9\xa\2\0\4"];} // Who said my code was obscure?




More information about the Python-list mailing list