[ACCEPTED]-Python assert -- improved introspection of failure?-syntactic-sugar

Accepted answer
Score: 11

Install your of function as sys.excepthook -- see the docs. Your 15 function, if the second argument is AssertionError, can 14 introspect to your heart's contents; in 13 particular, through the third argument, the 12 traceback, it can get the frame and exact 11 spot in which the assert failed, getting 10 the failing exception through the source 9 or bytecode, the value of all relevant variables, etc. Module 8 inspect helps.

Doing it in full generality is quite 7 a piece of work, but depending on what constraints 6 you're willing to accept in how you write 5 your asserts it can be lightened substantially 4 (e.g. restricting them to only local or 3 global variables makes introspection easier 2 than if nonlocal variables of a closure 1 could be involved, and so forth).

Score: 7

You can attach a message to an assert:

assert 6-(3*2), "always fails"

The message 2 can also be built dynamically:

assert x != 0, "x is not equal to zero (%d)" % x

See The assert statement in the 1 Python documentation for more information.

Score: 7

As @Mark Rushakoff said nose can evaluate failed asserts. It works 1 on the standard assert too.

# test_error_reporting.py
def test():
    a,b,c = 6, 2, 3
    assert a - b*c

nosetests' help:

$ nosetests --help|grep -B2 assert
  -d, --detailed-errors, --failure-detail
                        Add detail to error output by attempting to evaluate
                        failed asserts [NOSE_DETAILED_ERRORS]

Example:

$ nosetests -d
F
======================================================================
FAIL: test_error_reporting.test
----------------------------------------------------------------------
Traceback (most recent call last):
  File "..snip../site-packages/nose/case.py", line 183, in runTest
    self.test(*self.arg)
  File "..snip../test_error_reporting.py", line 3, in test
    assert a - b*c
AssertionError:
    6,2,3 = 6, 2, 3
>>  assert 6 - 2*3


----------------------------------------------------------------------
Ran 1 test in 0.089s

FAILED (failures=1)
Score: 4

The nose testing suite applies introspection to asserts.

However, AFAICT, you have to call their asserts 5 to get the introspection:

import nose
def test1():
    nose.tools.assert_equal(6, 5+2)

results in

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 3, in test1
    nose.tools.assert_equal(6, 5+2)
AssertionError: 6 != 7
>>  raise self.failureException, \
          (None or '%r != %r' % (6, 7))

Notice 4 the AssertionError there. When my line was 3 just assert 6 == 5+2, I would get:

C:\temp\py>C:\Python26\Scripts\nosetests.exe -d test.py
F
======================================================================
FAIL: test.test1
----------------------------------------------------------------------
Traceback (most recent call last):
  File "C:\Python26\lib\site-packages\nose-0.11.1-py2.6.egg\nose\case.py", line
183, in runTest
    self.test(*self.arg)
  File "C:\temp\py\test.py", line 2, in test1
    assert 6 == 5 + 2
AssertionError:
>>  assert 6 == 5 + 2

Also, I'm not sure offhand 2 if their asserts are skipped with -O, but 1 that would be a very quick check.

Score: 3

I coded a replacement for sys.excepthook (which is called 10 for any unhandled exception) which is a 9 bit more fancy than the standard one. It 8 will analyze the line where the exception 7 occured and print all variables which are 6 referred to in this line (it does not print 5 all local variables because that might be 4 too much noise - also, maybe the important 3 var is global or so).

I called it py_better_exchook 2 (perfect name) and it's here.

Example file:

a = 6

def test():
    unrelated_var = 43
    b,c = 2, 3
    assert a - b*c

import better_exchook
better_exchook.install()

test()

Output:

$ python test_error_reporting.py 
EXCEPTION
Traceback (most recent call last):
  File "test_error_reporting.py", line 12, in <module>
    line: test()
    locals:
      test = <local> <function test at 0x7fd91b1a05f0>
  File "test_error_reporting.py", line 7, in test
    line: assert a - b*c
    locals:
      a = <global> 6
      b = <local> 2
      c = <local> 3
AssertionError

There 1 are a few other alternatives:

Score: 0

Add a message to your assertion, which will 8 be displayed if the assertion fails:

$ python -c "assert 6-(3*2), '6-(3*2)'"
Traceback (most recent call last):
  File "<string>", line 1, in <module>
AssertionError: 6-(3*2)

The 7 only way I can think of to provide this 6 automatically would be to contain the assertion 5 in a procedure call, and then inspect the 4 stack to get the source code for that line. The 3 additional call would, unfortunately, introduce 2 overhead into the test and would not be 1 disabled with -O.

Score: 0

It sounds like what you really want to do 3 is to set up a debugger breakpoint just 2 before the assert and inspect from your favorite 1 debugger as much as you like.

More Related questions