Python assert -- improved introspection of failure? - python

This is a rather useless assertion error; it does not tell the values of the expression involved (assume constants used are actually variable names):
$ python -c "assert 6-(3*2)"
[...]
AssertionError
Is there a better assert implementation in Python that is more fancy? It must not introduce additional overhead over execution (except when assert fails) .. and must turn off if -O flag is used.
Edit: I know about assert's second argument as a string. I don't want to write one .. as that is encoded in the expression that is being asserted. DRY (Don't Repeat Yourself).

Install your of function as sys.excepthook -- see the docs. Your function, if the second argument is AssertionError, can introspect to your heart's contents; in particular, through the third argument, the traceback, it can get the frame and exact spot in which the assert failed, getting the failing exception through the source or bytecode, the value of all relevant variables, etc. Module inspect helps.
Doing it in full generality is quite a piece of work, but depending on what constraints you're willing to accept in how you write your asserts it can be lightened substantially (e.g. restricting them to only local or global variables makes introspection easier than if nonlocal variables of a closure could be involved, and so forth).

You can attach a message to an assert:
assert 6-(3*2), "always fails"
The message can also be built dynamically:
assert x != 0, "x is not equal to zero (%d)" % x
See The assert statement in the Python documentation for more information.

As #Mark Rushakoff said nose can evaluate failed asserts. It works 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)

The nose testing suite applies introspection to asserts.
However, AFAICT, you have to call their asserts 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 the AssertionError there. When my line was 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 if their asserts are skipped with -O, but that would be a very quick check.

I coded a replacement for sys.excepthook (which is called for any unhandled exception) which is a bit more fancy than the standard one. It will analyze the line where the exception occured and print all variables which are referred to in this line (it does not print all local variables because that might be too much noise - also, maybe the important var is global or so).
I called it py_better_exchook (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 are a few other alternatives:
(Presented here) https://github.com/albertz/py_better_exchook/
https://github.com/patrys/great-justice
Nose does something similar for assertion failures, see here.
IPython has something similar (this). Do this: from IPython.core import ultratb; sys.excepthook = ultratb.VerboseTB().
Ka-Ping Yee's "cgitb.py", which is part of Python, see here, code here.

Add a message to your assertion, which will 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 only way I can think of to provide this automatically would be to contain the assertion in a procedure call, and then inspect the stack to get the source code for that line. The additional call would, unfortunately, introduce overhead into the test and would not be disabled with -O.

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

Related

Doctest fails in python3 but works if tested manually

I have just started learning python and I am following some online lectures from my university. I have just been introduced to writing doctests in scripts. Below is my code:
""" Our first python source file. """
from operator import floordiv, mod
def divide_exact(n,d):
""" Return the quotient and remainder.
>>> q, r = divide_exact(2013, 10)
>>> q
201
>>> r
3
"""
return floordiv(n, d), mod(n, d)
When I run
python3 -m doctest -v lec3video3
I pass 0 testcases. I get this error
File "lec3video3test", line 7, in lec3video3test
Failed example:
q, r = divide_exact(2013, 10)
Exception raised:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/doctest.py", line 1329, in __run
compileflags, 1), test.globs)
File "<doctest lec3video3test[0]>", line 1, in <module>
q, r = divide_exact(2013, 10)
NameError: name 'divide_exact' is not defined
I don't know why because I know my code works. What am I doing wrong? I have tested my code in interactive mode and everything is correct.
There is an IndentationError in the current version of your code. However, I guess that's just an error which happened during copy and paste as you would get a completely different error message, then.
Regarding the NameError you're describing: If I put your given code
""" Our first python source file. """
from operator import floordiv, mod
def divide_exact(n,d):
""" Return the quotient and remainder.
>>> q, r = divide_exact(2013, 10)
>>> q
201
>>> r
3
"""
return floordiv(n, d), mod(n, d)
in a file example.py and call doctest with the following command, I get:
$ python3 -m doctest -v example.py
Trying:
q, r = divide_exact(2013, 10)
Expecting nothing
ok
Trying:
q
Expecting:
201
ok
Trying:
r
Expecting:
3
ok
1 items had no tests:
example
1 items passed all tests:
3 tests in example.divide_exact
3 tests in 2 items.
3 passed and 0 failed.
Test passed.
It says
1 items had no tests:
example
because there are no tests in the module docstring. Other than that, all three tests in the function docstring for divide_exact do pass as expected.
So you will just have to make sure your file is in the correct folder and your command follows the required syntax for the doctest module.

python unittest assertRaises

I copied this verbatim from python.org unittest documentation:
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def test_shuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
# should raise an exception for an immutable sequence
self.assertRaises(TypeError, random.shuffle, (1,2,3))
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
if __name__ == '__main__':
unittest.main()
But I get this error message from python 2.7.2 [GCC 4.1.2 20080704 (Red Hat 4.1.2-51)] on linux2:
.E.
======================================================================
ERROR: test_sample (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "tmp.py", line 23, in test_sample
with self.assertRaises(ValueError):
TypeError: failUnlessRaises() takes at least 3 arguments (2 given)
----------------------------------------------------------------------
Ran 3 tests in 0.001s
FAILED (errors=1)
How can I get assertRaises() to work properly?
The ability to use unittest.TestCase.AssertRaises() as context manager was added in python 2.7.
http://docs.python.org/2/library/unittest.html#unittest.TestCase.assertRaises
Check that you are really using 2.7 python.
Tested using pythonbrew:
$ pythonbrew use 2.7.2
$ python test.py
...
----------------------------------------------------------------------
Ran 3 tests in 0.000s
OK
$ pythonbrew use 2.6.5
$ python test.py
.E.
======================================================================
ERROR: test_sample (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 23, in test_sample
with self.assertRaises(ValueError):
TypeError: failUnlessRaises() takes at least 3 arguments (2 given)
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (errors=1)
If you're using 2.7 and still seeing this issue, it could be because you're not using python's unittest module. Some other modules like twisted provide assertRaises and though they try to maintain compatibility with python's unittest, your particular version of that module may be out of date.

import unittest error

This is my first time doing unit testing and i'm trying to run a simple code...
import random
import unittest
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
def test_shuffle(self):
# make sure the shuffled sequence does not lose any elements
random.shuffle(self.seq)
self.seq.sort()
self.assertEqual(self.seq, range(10))
# should raise an exception for an immutable sequence
self.assertRaises(TypeError, random.shuffle, (1,2,3))
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
def test_sample(self):
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)
then i get the error message...
Traceback (most recent call last):
File "/Users/s66/Desktop/unittest.py", line 2, in <module>
import unittest
File "/Users/s66/Desktop/unittest.py", line 4, in <module>
class TestSequenceFunctions(unittest.TestCase):
AttributeError: 'module' object has no attribute 'TestCase'
>>>
How do I fix this?
That's because your script name is called unittest.py. The statement import unittest is importing your script rather than the unittest module, hence the error with the non-existant TestCase attribute.
For more info, see the docs for Module Search Path. In short, when you do an import, built-in modules are first searched followed by directories listed in sys.path. This usually starts with the location of the running script followed by PYTHONPATH and THEN then default modules directory
In your case, since unittest is not a built-in, it found your script (and loaded it) before it could search for other installed modules.
How do I fix this?
Rename your script.
Do you have a module in the directory where you're running your script that is called unittest.py?
When I run your code I get the following output.
test_choice (__main__.TestSequenceFunctions) ... ok
test_sample (__main__.TestSequenceFunctions) ... ERROR
test_shuffle (__main__.TestSequenceFunctions) ... ok
======================================================================
ERROR: test_sample (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 23, in test_sample
with self.assertRaises(ValueError):
TypeError: failUnlessRaises() takes at least 3 arguments (2 given)
----------------------------------------------------------------------
Ran 3 tests in 0.000s
FAILED (errors=1)
My answer is not intended for this author, but for people that found this stackoverflow page on Google for the same error message but for a different reason.
I was upgrading from django 1.5 to django 1.10, and the "import unittest" in one of my test files was causing a similar error.
The error was due to the app/tests/init.py file which was importing the errored test file (this was necessary in django 1.5 as the automatic detection of test files didn't exist at that time. Now it is no longer required).
So the solution was to simply empty the init.py file and it solved the issue.
Hope it can help some people as I lost 30mn to 1h on this issue.

e.printStackTrace equivalent in python

I know that print(e) (where e is an Exception) prints the occurred exception
but, I was trying to find the python equivalent of Java's e.printStackTrace() that exactly traces the exception to what line it occurred and prints the entire trace of it.
Could anyone please tell me the equivalent of e.printStackTrace() in Python?
import traceback
traceback.print_exc()
When doing this inside an except ...: block it will automatically use the current exception. See http://docs.python.org/library/traceback.html for more information.
There is also logging.exception.
import logging
...
try:
g()
except Exception as ex:
logging.exception("Something awful happened!")
# will print this message followed by traceback
Output:
ERROR 2007-09-18 23:30:19,913 error 1294 Something awful happened!
Traceback (most recent call last):
File "b.py", line 22, in f
g()
File "b.py", line 14, in g
1/0
ZeroDivisionError: integer division or modulo by zero
(From http://blog.tplus1.com/index.php/2007/09/28/the-python-logging-module-is-much-better-than-print-statements/ via How to print the full traceback without halting the program?)
e.printStackTrace equivalent in python
In Java, this does the following (docs):
public void printStackTrace()
Prints this throwable and its backtrace to the standard error stream...
This is used like this:
try
{
// code that may raise an error
}
catch (IOException e)
{
// exception handling
e.printStackTrace();
}
In Java, the Standard Error stream is unbuffered so that output arrives immediately.
The same semantics in Python 2 are:
import traceback
import sys
try: # code that may raise an error
pass
except IOError as e: # exception handling
# in Python 2, stderr is also unbuffered
print >> sys.stderr, traceback.format_exc()
# in Python 2, you can also from __future__ import print_function
print(traceback.format_exc(), file=sys.stderr)
# or as the top answer here demonstrates, use:
traceback.print_exc()
# which also uses stderr.
Python 3
In Python 3, we can get the traceback directly from the exception object (which likely behaves better for threaded code).
Also, stderr is line-buffered, but the print function gets
a flush argument, so this would be immediately printed to stderr:
print(traceback.format_exception(None, # <- type(e) by docs, but ignored
e, e.__traceback__),
file=sys.stderr, flush=True)
Conclusion:
In Python 3, therefore, traceback.print_exc(), although it uses sys.stderr by default, would buffer the output, and you may possibly lose it. So to get as equivalent semantics as possible, in Python 3, use print with flush=True.
Adding to the other great answers, we can use the Python logging library's debug(), info(), warning(), error(), and critical() methods. Quoting from the docs for Python 3.7.4,
There are three keyword arguments in kwargs which are inspected: exc_info which, if it does not evaluate as false, causes exception information to be added to the logging message.
What this means is, you can use the Python logging library to output a debug(), or other type of message, and the logging library will include the stack trace in its output. With this in mind, we can do the following:
import logging
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
def f():
a = { 'foo': None }
# the following line will raise KeyError
b = a['bar']
def g():
f()
try:
g()
except Exception as e:
logger.error(str(e), exc_info=True)
And it will output:
'bar'
Traceback (most recent call last):
File "<ipython-input-2-8ae09e08766b>", line 18, in <module>
g()
File "<ipython-input-2-8ae09e08766b>", line 14, in g
f()
File "<ipython-input-2-8ae09e08766b>", line 10, in f
b = a['bar']
KeyError: 'bar'

Combining nosetests with contracts for Python

I'm using contracts for Python to specify preconditons/postconditions/invariants. I'm also using doctests for doing unit testing.
I'd like to have all of my doctest unit tests run with contracts enabled, and I'd like to run my tests using nose. Unfortunately, if I run the tests with nose, it does not execute the pre/post/invariant assertions. I put a setup function in each .py file to make sure that contract.checkmod gets called
def setup():
import contract
contract.checkmod(__name__)
I can confirm that this function is being executed by nose before it runs the tests, but the contracts still don't get executed.
On the other hand, if I run the doctest by calling doctest.testmod, the pre/post/inv do get called:
def _test():
import contract
contract.checkmod(__name__)
import doctest
doctest.testmod()
if __name__=='__main__':
_test()
Here's an example of a Python script whose test will succeed if called directly, but failed if called with nose:
import os
def setup():
import contract
contract.checkmod(__name__)
def delete_file(path):
"""Delete a file. File must be present.
>>> import minimock
>>> minimock.mock('os.remove')
>>> minimock.mock('os.path.exists', returns=True)
>>> delete_file('/tmp/myfile.txt')
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
>>> minimock.restore()
pre: os.path.exists(path)
"""
os.remove(path)
if __name__ == '__main__':
setup()
import doctest
doctest.testmod()
When I run the above file standalone, the tests pass:
$ python contracttest.py -v
Trying:
import minimock
Expecting nothing
ok
Trying:
minimock.mock('os.remove')
Expecting nothing
ok
Trying:
minimock.mock('os.path.exists', returns=True)
Expecting nothing
ok
Trying:
delete_file('/tmp/myfile.txt')
Expecting:
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
ok
Trying:
minimock.restore()
Expecting nothing
ok
2 items had no tests:
__main__
__main__.setup
1 items passed all tests:
5 tests in __main__.delete_file
5 tests in 3 items.
5 passed and 0 failed.
Test passed.
Here it is with nose:
$ nosetests --with-doctest contracttest.py
F
======================================================================
FAIL: Doctest: contracttest.delete_file
----------------------------------------------------------------------
Traceback (most recent call last):
File "/System/Library/Frameworks/Python.framework/Versions/2.6/lib/python2.6/doctest.py", line 2131, in runTest
raise self.failureException(self.format_failure(new.getvalue()))
AssertionError: Failed doctest test for contracttest.delete_file
File "/Users/lorin/Desktop/contracttest.py", line 10, in delete_file
----------------------------------------------------------------------
File "/Users/lorin/Desktop/contracttest.py", line 17, in contracttest.delete_file
Failed example:
delete_file('/tmp/myfile.txt')
Expected:
Called os.path.exists('/tmp/myfile.txt')
Called os.remove('/tmp/myfile.txt')
Got:
Called os.remove('/tmp/myfile.txt')
----------------------------------------------------------------------
Ran 1 test in 0.055s

Categories