How to capture assert in python script - python

Is there any way in Python2.7, we can capture and log the assert statements in general python scripting despite of assert is True or False
Suppose I assert following line in code:
assert len(lst) ==4
So is any way can log what statement is passed, at what line and it is true or false. I do not want to use a wrapper function, looking for something inbuilt in python .
Note: What I want to achieve is , suppose I have legacy code having 1000's of assert statements , without changing the code, I should be able to log which assert statement is executed and what is the output, Is its achievable in python 2.7.

try:
assert len(lst) == 4
print "True"
except AssertionError as e:
print "False"

Yes, you can define a custom excepthook to log some extra information:
import sys
import logging
def excepthook(*args):
logging.getLogger().error('Uncaught exception:', exc_info=args)
sys.excepthook = excepthook
assert 1==2
EDIT: Whoops I forgot you wanted to log even if it's true :) oh well I'll leave this for a little bit in case it informs you or someone else...

This is what I could achieve closet, as does not seem it is possible in Python2.7 . I created a wrapper function.
import inspect
def assertit(condition,message):
# The caller method
(frame, filename, line_number,function_name, lines, index) = inspect.getouterframes(inspect.currentframe())[1]
detailed_message=" => {message} [ {filename} : {line_number}]".format(message=message,line_number=line_number,
filename=filename)
if condition:
print "True %s"%detailed_message
return
raise AssertionError("False: %s"%detailed_message)
assertit(1==1,"Check if 1 equal 1")
assertit(1==2,"Check if 1 equal 2")
### HERE IS THE OUTPUT
True => Check if 1 equal 1 [ test_cases.py : 20]
Traceback (most recent call last):
File "test_cases.py", line 21, in <module>
assertit(1==2,"Check if 1 equal 2")
File "test_cases.py", line 19, in assertit
raise AssertionError("False: %s"%detailed_message)
AssertionError: False: => Check if 1 equal 2 [ test_cases.py : 21]

This is a proof of concept. So please downvote, or I will delete it...
The idea is to replace the assert statement with print statement at the execution.
import ast
import inspect
from your_module import main
def my_assert(test_result, msg):
assert test_result, msg
# if test_result is True, just print the msg
return "assert: {}, {}".format(test_result, msg)
class Transformer(ast.NodeTransformer):
def visit_Assert(self, node):
f = ast.Name(id='my_assert', ctx=ast.Load())
c = ast.Call(func=f, args=[node.test, node.msg], keywords=[])
p = ast.Print(values=[c], nl=True)
# set line#, column offset same as the original assert statement
f.lineno = c.lineno = p.lineno = node.lineno
f.col_offset =c.col_offset = p.col_offset = node.col_offset
return p
def replace_assert_with_print(func):
source = inspect.getsource(func)
ast_tree = ast.parse(source)
Transformer().visit(ast_tree)
return compile(ast_tree, filename="<ast>", mode="exec")
if __name__ == '__main__':
exec(replace_assert_with_print(main))
main(4)
And here is your_module.py
def main(x):
assert x == 4, "hey {} is not 4".format(x)
return x

Related

Can assertions compare against print statments?

Can assertions compare against print statements in python like this:
def printname(name):
print(name)
#to not raise an error:
assert printname("Hello") == "Hello"
#to raise an error:
assert printname("Hello") == "notHello"
No, assertions can't do that. If you want to do that try this:
def printname(name):
print(name)
return name
#to not raise an error:
assert printname("Hello") == "Hello"
#to raise an error:
assert printname("Hello") == "notHello"
This technically doesn't compare it, but its a valid workaround. If you really want to compare them take a look into StringIO.
Try using mock + stringIO to capture stdout/err:
from mock import patch
from StringIO import StringIO
def test_foobar():
out, err = StringIO(), StringIO()
with patch.multiple(sys, stdout=out, stderr=err):
do_stuff()
assert out.getvalue() == 'foo'
assert err.getvalue() == 'bar'
Also if you are doing this within a test framework, you may be able to do a variety of things. In particular for pytest:
def test_myoutput(capsys): # or use "capfd" for fd-level
print("hello")
sys.stderr.write("world\n")
captured = capsys.readouterr()
assert captured.out == "hello\n"
assert captured.err == "world\n"
print("next")
captured = capsys.readouterr()
assert captured.out == "next\n"
No. The function printname doesn't have an explicit return statement, so it return the value . So the assert compares None against a string and of course it raise an assertion error.

How to assert all asserts failed in python

I'm trying to write examples of how NOT to write tests in pytest. For this, I'd like to be able to somehow mod test functions so that they fail if any assert passes. This is easy with one assert using a decorator:
def faulty_test(test_fn):
def new_test_fn():
try:
test_fn()
except AssertionError:
pass
else:
raise AssertionError
return new_test_fn
#faulty_test
def test_sth():
assert 1 == 2
but I'd like to do it for a function with any number of asserts. The test should fail if any of the asserts pass. It doesn't have to be a decorator
Perhaps you should think about it this way: At the point of the exception, execution leaves the try block and enters the except block. You cannot, at least in any simple way I can think of, re-enter the try block from within the except block.
And perhaps I'm misunderstanding the question, but couldn't you simply replace your assertions with the inverse of those assertions? If you want to assert that a thing is false, you can:
assert not (1 == 2)
If for some reason you're dealing with a situation where you assume that the given functions will, for some reason, always fail their asserts and you cannot change that, there does not seem to be any way to ensure all the asserts run, since the design of assertions is to intentionally abort immediately on failure.
I guess it doesn't have to be multiple asserts. You can write:
assert a == b or c == d or # so on
Any condition is True will cause the assertion to pass.
If you "number" the assertions, catching an assertion with a value greater than 0 implies the previous assertions passed.
def faulty_test(test_fn):
def new_test_fn():
try:
test_fn()
except AssertionError as exc:
if exc.args[0] > 0:
# Assertions 0 through exc.args[0]-1 all passed
raise AssertionError
else:
raise AssertionError
return new_test_fn
#faulty_test
def test_sth():
assert 1 == 2, 0
assert 2 == 3, 1
assert 3 == 4, 2
You should probably handle that in the function code by using a _debug or other (hidden) argument. Then you can use a decorator on that. What I would do:
def deco(inF):
def wrapper(*args, **kwargs):
kwargs['_debug'] = True
output = inF(*args, **kwargs)
#print(output)
for i, _ in enumerate(zip(*output)):
condition, msg = _
# this will raise the first passed assert. Otherwise loop again to print all "passed" assertions, then raise
if not condition:
raise AssertionError('Condition {} succeded: {} is False'.format(1 + i, msg))
return output
return wrapper
#deco
def f(i, j , k, _debug=False):
cond1, msg1 = i == 1, 'i is not 1'
cond2, msg2 = j == 2, 'j is not 2'
cond3, msg3 = k == 3, 'k is not 3'
assert cond1 or _debug, msg1
assert cond2 or _debug, msg2
assert cond3 or _debug, msg3
if _debug:
return (cond1, cond2, cond3), (msg1, msg2, msg3)
return i + j + k
f(1,1,5)
>>AssertionError: Condition 2 succeded: j is not 2 is False

Use decorators to wrap all functions with "if func returned false, return false"

I'm writing a very basic Python script based on a main function that sequentially calls other functions.
What I would like to do is to wrap all of the functions that are called from main with something like:
result = func(*some_args):
if (result != True):
return result
For example, for this code:
def func1(arg1 , arg2):
if (some_cond):
return False #Or some err_val
return True
def func2(arg1):
if (some_cond):
return False
return True
def main():
func1(val1 , val2)
func2(val3)
return True
if __name__ == "__main__":
import sys
result = main()
if (result == err_val1):
# Do something. Maybe print. Maybe call some function.
sys.exit(1)
I want that if one of the functions fails main would break and return its error. Can I do this using decorators?
I maintain that the best solution would be the use of exceptions. If that's absolutely not what you want, you can do some short-circuiting:
return func1() and func2()
To extend this to more functions without a ton of ands:
from functools import partial
def main():
funcs = (partial(func1, arg1, arg2),
partial(func2, arg1))
if any(!f() for f in funcs):
return False
Though this doesn't return "its error" (the failed function's error), it just returns False. If you want to differentiate more between different kinds of errors... well, you're back to exceptions.
This is precisely what exceptions are built for in Python.
# imports belong at the *top* of the file
import sys
class SomeDescriptiveError(Exception): pass
class SomeOtherSpecialError(Exception): pass
def func1(arg1 , arg2):
if (some_cond):
raise SomeDescriptiveError('Cannot frobnosticate the fizzbuzz')
return arg1 + arg2
# or skip the return statement altogether
def func2(arg1):
if (some_cond):
raise SomeOtherSpecialError('The frontobulator is no longer cromulent')
return ''.join(reversed(arg1))
def main():
print(func1(val1 , val2))
print(func2(val3))
if __name__ == "__main__":
try:
result = main()
except SomeDescriptiveError as e:
print('Oh dear')
sys.exit(e.args[0])
except SomeOtherSpecialError as e:
print('Oh no')
sys.exit(e.args[0])
else:
print('All systems are fully operational')
finally:
print('I really should clean up all these bits.')
Since you do actually want the program to die when one of these errors occurs you might as well raise SystemExit. Here's a way to do it with a decorator.
flag = 2
def die_on_not_True(func):
def wrapper(*args):
rc = func(*args)
if rc is not True:
fmt = 'Function {} failed with return value {!r}'
print(fmt.format(func.__name__, rc))
raise SystemExit(1)
return True
return wrapper
#die_on_not_True
def func1(arg1 , arg2):
if arg1 == flag:
return 'error 1'
return True
#die_on_not_True
def func2(arg1):
if arg1 == flag:
return 'error 2'
return True
def main():
val1, val2, val3 = 1, 2, 3
print(func1(val1, val2))
print('one')
print(func2(val3))
print('two')
if __name__ == '__main__':
main()
output
True
one
True
two
If we set flag = 1, the output becomes
Function func1 failed with return value 'error 1'
If we set flag = 3, the output becomes
True
one
Function func2 failed with return value 'error 2'
When flag equals 2, the exit status of 0 is returned to the shell, when flag equals 1 or 3, the exit status of 1 is returned.
If you want to do further processing after printing the error message, then raise a custom exception instead of SystemExit and catch it by wrapping your main call in a try...except.
I guess, what you really want is a universal exception catcher, that would catch and return the exception of any wrapped function. You can easily do it this way.
def return_exception(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except Exception as e:
return e
return wrapper
Example
In [3]: #return_exception
...: def div(a, b):
...: return a / b
...:
In [4]: div(1, 0)
Out[4]: ZeroDivisionError('division by zero')
So then you can process the return exception object the way you want, though it's pretty hard to say why you need that.
Update As others have noted it's generally good to only catch particular exceptions. You can modify the decorator slightly.
def return_exception(*exception_types):
def build_wrapper(func):
def wrapper(*args, **kwargs):
try:
return func(*args, **kwargs)
except exception_types as e:
return e
return wrapper
return build_wrapper
Example:
In [6]: #return_exception(ZeroDivisionError)
...: def div(a, b):
...: return a / b
...:
In [7]: div(0, 1)
Out[7]: 0.0
In [8]: div(1, 0)
Out[8]: ZeroDivisionError('division by zero')
In [9]: div(1, "a")
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
...
TypeError: unsupported operand type(s) for /: 'int' and 'str'
In [10]: #return_exception(ZeroDivisionError, TypeError)
....: def div(a, b):
....: return a / b
....:
In [11]: div(1, 0)
Out[11]: ZeroDivisionError('division by zero')
In [12]: div(1, "a")
Out[12]: TypeError("unsupported operand type(s) for /: 'int' and 'str'")
As you see, you only catch the specified exceptions (you can still specify the universal Exception class, though).

Get value of last expression in `exec` call

Let's say I have some python code in a string
code = """
a = 42
a
"""
and I exec that string of code:
result = exec(code)
Then result will always be None. Is there any way at all to get the value of the last expression evaluated? In this case, that would be 5, since a was the last expression.
EDIT: Here's another example of the functionality I'm asking about. Let's say we have the python code (stored in the variable code)
a = 100
sqrt(a)
Then how can I execute the code in such a way as to give me the result 10 - that is, sqrt(a)?
EDIT EDIT: A further example: the code I wish to exec is
function_a()
function_b()
function_c()
Is there any way I can define some kind of magic_exec function so that
magic_exec(code)
will provide me with the value of function_c()?
The request is certainly valid because I need such a function as well during the creation of a Python-based environment. I solved the problem with the following code that utilizes the Python ast mechanism:
def my_exec(script, globals=None, locals=None):
'''Execute a script and return the value of the last expression'''
stmts = list(ast.iter_child_nodes(ast.parse(script)))
if not stmts:
return None
if isinstance(stmts[-1], ast.Expr):
# the last one is an expression and we will try to return the results
# so we first execute the previous statements
if len(stmts) > 1:
exec(compile(ast.Module(body=stmts[:-1]), filename="<ast>", mode="exec"), globals, locals)
# then we eval the last one
return eval(compile(ast.Expression(body=stmts[-1].value), filename="<ast>", mode="eval"), globals, locals)
else:
# otherwise we just execute the entire code
return exec(script, globals, locals)
The code should be pretty self-explanatory, basically it
separate the script into multiple statements
if the last one is an expression, execute the first part as statements, and the last part as expression.
Otherwise execute the entire script as statements.
This doesn't get you the last evaluated value, but gets the whole list of local variables.
>>> loc = {}
>>> exec(code, {}, loc)
>>> loc
{'a': 42}
exec('a = 4')
print a % prints 4
>>> code = """
... a = 42
... b = 53"""
>>> exec(code)
>>> a
42
>>> b
53
Or if you're saying you don't know the last thing is b for instance, then you can have this:
code = """
a = 4
b = 12
abc_d=13
"""
t = re.findall(r'''.*?([A-Za-z0-9_]+)\s*?=.*?$''', code)
assert(len(t)==1)
print t[0] % prints 13
To be honest I can't say I'm very happy with this. It feels very hacky and I haven't tested it all that heavily. On the other hand I'm quite pleased with it. Was quite fun to do. Anyway, hope this helps you or at least comes close to what you want. locals() gives a dict so the output list order does not match the input order for the items that failed the first eval. If you don't want ';' as delimiters then you can change it to '\n'.
import math
def magic_exec(_command):
_command = _command.split(';')
_result = None
_before = list(locals()) # Get list of current local variables
for _code in _command:
_code = _code.strip() # .strip() prevent IndentationError
try:
if eval(_code) != None: # For functions with no return
_result = eval(_code)
except (NameError, SyntaxError):
try:
_before = list(locals())
exec(_code)
except NameError as e: # For undefined variables in _command
print("An Error Occurred with line ' {0} ' as was skipped: {1}".format(_code, e))
del _code # delete temp var _code
# Get new list of locals that didn't exist at the start
_after = [val for val in list(locals()) if val not in _before]
if _after:
return eval(_after[0])
else:
return _result
#Dummy class and functions
class Class1(object):
def __init__(self, x):
self._x = x
def get_val(self):
return self._x
def __repr__(self):
return type(self).__name__
def func1(x):
return x + x
def func2(x):
print(x*x)
if __name__ == '__main__':
code = \
"""
a = 42; a; v; y = 2; b = func1(5); s = 'Hello'; func2(10); c = 25; l = []; l.append('Value');
t = math.sqrt(c); pass; 20*10; print('TEST'); math.sqrt(c); d = Class1('World'); d.get_val();
def func3(x): return x ** 2; s = func3(15)
"""
values = magic_exec(code)
print(values)
I would like to add to user2283347's excellent answer that it works only up to Python 3.7. In Python 3.8 the signature of ast.Module.__init__ has changed. It now requires a second argument which in our case can be an empty list.
Details: ast.Module(body=stmts[:-1]) in
if len(stmts) > 1:
exec(compile(ast.Module(body=stmts[:-1]), filename="<ast>", mode="exec"), globals, locals)
has to be changed to
ast.Module(stmts[:-1], []) if you use Python 3.8 or above (note the second argument []). Otherwise the following TypeError will be raised:
TypeError: required field "type_ignores" missing from Module
Unfortunately this change is not very well documented. I found the solution after extensive Googling here: "IPython broken on 3.8-dev" .

Outputting data from unit test in Python

If I'm writing unit tests in Python (using the unittest module), is it possible to output data from a failed test, so I can examine it to help deduce what caused the error?
I am aware of the ability to create a customized message, which can carry some information, but sometimes you might deal with more complex data, that can't easily be represented as a string.
For example, suppose you had a class Foo, and were testing a method bar, using data from a list called testdata:
class TestBar(unittest.TestCase):
def runTest(self):
for t1, t2 in testdata:
f = Foo(t1)
self.assertEqual(f.bar(t2), 2)
If the test failed, I might want to output t1, t2 and/or f, to see why this particular data resulted in a failure. By output, I mean that the variables can be accessed like any other variables, after the test has been run.
We use the logging module for this.
For example:
import logging
class SomeTest( unittest.TestCase ):
def testSomething( self ):
log= logging.getLogger( "SomeTest.testSomething" )
log.debug( "this= %r", self.this )
log.debug( "that= %r", self.that )
self.assertEqual( 3.14, pi )
if __name__ == "__main__":
logging.basicConfig( stream=sys.stderr )
logging.getLogger( "SomeTest.testSomething" ).setLevel( logging.DEBUG )
unittest.main()
That allows us to turn on debugging for specific tests which we know are failing and for which we want additional debugging information.
My preferred method, however, isn't to spend a lot of time on debugging, but spend it writing more fine-grained tests to expose the problem.
In Python 2.7 you could use an additional parameter, msg, to add information to the error message like this:
self.assertEqual(f.bar(t2), 2, msg='{0}, {1}'.format(t1, t2))
The official documentation is here.
You can use simple print statements, or any other way of writing to standard output. You can also invoke the Python debugger anywhere in your tests.
If you use nose to run your tests (which I recommend), it will collect the standard output for each test and only show it to you if the test failed, so you don't have to live with the cluttered output when the tests pass.
nose also has switches to automatically show variables mentioned in asserts, or to invoke the debugger on failed tests. For example, -s (--nocapture) prevents the capture of standard output.
I don't think this is quite what you're looking for. There's no way to display variable values that don't fail, but this may help you get closer to outputting the results the way you want.
You can use the TestResult object returned by the TestRunner.run() for results analysis and processing. Particularly, TestResult.errors and TestResult.failures
About the TestResults Object:
http://docs.python.org/library/unittest.html#id3
And some code to point you in the right direction:
>>> import random
>>> import unittest
>>>
>>> class TestSequenceFunctions(unittest.TestCase):
... def setUp(self):
... self.seq = range(5)
... def testshuffle(self):
... # make sure the shuffled sequence does not lose any elements
... random.shuffle(self.seq)
... self.seq.sort()
... self.assertEqual(self.seq, range(10))
... def testchoice(self):
... element = random.choice(self.seq)
... error_test = 1/0
... self.assert_(element in self.seq)
... def testsample(self):
... self.assertRaises(ValueError, random.sample, self.seq, 20)
... for element in random.sample(self.seq, 5):
... self.assert_(element in self.seq)
...
>>> suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
>>> testResult = unittest.TextTestRunner(verbosity=2).run(suite)
testchoice (__main__.TestSequenceFunctions) ... ERROR
testsample (__main__.TestSequenceFunctions) ... ok
testshuffle (__main__.TestSequenceFunctions) ... FAIL
======================================================================
ERROR: testchoice (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<stdin>", line 11, in testchoice
ZeroDivisionError: integer division or modulo by zero
======================================================================
FAIL: testshuffle (__main__.TestSequenceFunctions)
----------------------------------------------------------------------
Traceback (most recent call last):
File "<stdin>", line 8, in testshuffle
AssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
----------------------------------------------------------------------
Ran 3 tests in 0.031s
FAILED (failures=1, errors=1)
>>>
>>> testResult.errors
[(<__main__.TestSequenceFunctions testMethod=testchoice>, 'Traceback (most recent call last):\n File "<stdin>"
, line 11, in testchoice\nZeroDivisionError: integer division or modulo by zero\n')]
>>>
>>> testResult.failures
[(<__main__.TestSequenceFunctions testMethod=testshuffle>, 'Traceback (most recent call last):\n File "<stdin>
", line 8, in testshuffle\nAssertionError: [0, 1, 2, 3, 4] != [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]\n')]
>>>
The method I use is really simple. I just log it as a warning so it will actually show up.
import logging
class TestBar(unittest.TestCase):
def runTest(self):
#this line is important
logging.basicConfig()
log = logging.getLogger("LOG")
for t1, t2 in testdata:
f = Foo(t1)
self.assertEqual(f.bar(t2), 2)
log.warning(t1)
In these cases I use a log.debug() with some messages in my application. Since the default logging level is WARNING, such messages don't show in the normal execution.
Then, in the unit test I change the logging level to DEBUG, so that such messages are shown while running them.
import logging
log.debug("Some messages to be shown just when debugging or unit testing")
In the unit tests:
# Set log level
loglevel = logging.DEBUG
logging.basicConfig(level=loglevel)
See a full example:
This is daikiri.py, a basic class that implements a daikiri with its name and price. There is method make_discount() that returns the price of that specific daikiri after applying a given discount:
import logging
log = logging.getLogger(__name__)
class Daikiri(object):
def __init__(self, name, price):
self.name = name
self.price = price
def make_discount(self, percentage):
log.debug("Deducting discount...") # I want to see this message
return self.price * percentage
Then, I create a unit test, test_daikiri.py, that checks its usage:
import unittest
import logging
from .daikiri import Daikiri
class TestDaikiri(unittest.TestCase):
def setUp(self):
# Changing log level to DEBUG
loglevel = logging.DEBUG
logging.basicConfig(level=loglevel)
self.mydaikiri = Daikiri("cuban", 25)
def test_drop_price(self):
new_price = self.mydaikiri.make_discount(0)
self.assertEqual(new_price, 0)
if __name__ == "__main__":
unittest.main()
So when I execute it I get the log.debug messages:
$ python -m test_daikiri
DEBUG:daikiri:Deducting discount...
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
Another option - start a debugger where the test fails.
Try running your tests with Testoob (it will run your unit test suite without changes), and you can use the '--debug' command line switch to open a debugger when a test fails.
Here's a terminal session on Windows:
C:\work> testoob tests.py --debug
F
Debugging for failure in test: test_foo (tests.MyTests.test_foo)
> c:\python25\lib\unittest.py(334)failUnlessEqual()
-> (msg or '%r != %r' % (first, second))
(Pdb) up
> c:\work\tests.py(6)test_foo()
-> self.assertEqual(x, y)
(Pdb) l
1 from unittest import TestCase
2 class MyTests(TestCase):
3 def test_foo(self):
4 x = 1
5 y = 2
6 -> self.assertEqual(x, y)
[EOF]
(Pdb)
I think I might have been overthinking this. One way I've come up with that does the job, is simply to have a global variable that accumulates the diagnostic data.
Something like this:
log1 = dict()
class TestBar(unittest.TestCase):
def runTest(self):
for t1, t2 in testdata:
f = Foo(t1)
if f.bar(t2) != 2:
log1("TestBar.runTest") = (f, t1, t2)
self.fail("f.bar(t2) != 2")
Use logging:
import unittest
import logging
import inspect
import os
logging_level = logging.INFO
try:
log_file = os.environ["LOG_FILE"]
except KeyError:
log_file = None
def logger(stack=None):
if not hasattr(logger, "initialized"):
logging.basicConfig(filename=log_file, level=logging_level)
logger.initialized = True
if not stack:
stack = inspect.stack()
name = stack[1][3]
try:
name = stack[1][0].f_locals["self"].__class__.__name__ + "." + name
except KeyError:
pass
return logging.getLogger(name)
def todo(msg):
logger(inspect.stack()).warning("TODO: {}".format(msg))
def get_pi():
logger().info("sorry, I know only three digits")
return 3.14
class Test(unittest.TestCase):
def testName(self):
todo("use a better get_pi")
pi = get_pi()
logger().info("pi = {}".format(pi))
todo("check more digits in pi")
self.assertAlmostEqual(pi, 3.14)
logger().debug("end of this test")
pass
Usage:
# LOG_FILE=/tmp/log python3 -m unittest LoggerDemo
.
----------------------------------------------------------------------
Ran 1 test in 0.047s
OK
# cat /tmp/log
WARNING:Test.testName:TODO: use a better get_pi
INFO:get_pi:sorry, I know only three digits
INFO:Test.testName:pi = 3.14
WARNING:Test.testName:TODO: check more digits in pi
If you do not set LOG_FILE, logging will got to stderr.
You can use logging module for that.
So in the unit test code, use:
import logging as log
def test_foo(self):
log.debug("Some debug message.")
log.info("Some info message.")
log.warning("Some warning message.")
log.error("Some error message.")
By default warnings and errors are outputted to /dev/stderr, so they should be visible on the console.
To customize logs (such as formatting), try the following sample:
# Set-up logger
if args.verbose or args.debug:
logging.basicConfig( stream=sys.stdout )
root = logging.getLogger()
root.setLevel(logging.INFO if args.verbose else logging.DEBUG)
ch = logging.StreamHandler(sys.stdout)
ch.setLevel(logging.INFO if args.verbose else logging.DEBUG)
ch.setFormatter(logging.Formatter('%(asctime)s %(levelname)s: %(name)s: %(message)s'))
root.addHandler(ch)
else:
logging.basicConfig(stream=sys.stderr)
inspect.trace will let you get local variables after an exception has been thrown. You can then wrap the unit tests with a decorator like the following one to save off those local variables for examination during the post mortem.
import random
import unittest
import inspect
def store_result(f):
"""
Store the results of a test
On success, store the return value.
On failure, store the local variables where the exception was thrown.
"""
def wrapped(self):
if 'results' not in self.__dict__:
self.results = {}
# If a test throws an exception, store local variables in results:
try:
result = f(self)
except Exception as e:
self.results[f.__name__] = {'success':False, 'locals':inspect.trace()[-1][0].f_locals}
raise e
self.results[f.__name__] = {'success':True, 'result':result}
return result
return wrapped
def suite_results(suite):
"""
Get all the results from a test suite
"""
ans = {}
for test in suite:
if 'results' in test.__dict__:
ans.update(test.results)
return ans
# Example:
class TestSequenceFunctions(unittest.TestCase):
def setUp(self):
self.seq = range(10)
#store_result
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))
return {1:2}
#store_result
def test_choice(self):
element = random.choice(self.seq)
self.assertTrue(element in self.seq)
return {7:2}
#store_result
def test_sample(self):
x = 799
with self.assertRaises(ValueError):
random.sample(self.seq, 20)
for element in random.sample(self.seq, 5):
self.assertTrue(element in self.seq)
return {1:99999}
suite = unittest.TestLoader().loadTestsFromTestCase(TestSequenceFunctions)
unittest.TextTestRunner(verbosity=2).run(suite)
from pprint import pprint
pprint(suite_results(suite))
The last line will print the returned values where the test succeeded and the local variables, in this case x, when it fails:
{'test_choice': {'result': {7: 2}, 'success': True},
'test_sample': {'locals': {'self': <__main__.TestSequenceFunctions testMethod=test_sample>,
'x': 799},
'success': False},
'test_shuffle': {'result': {1: 2}, 'success': True}}
Catch the exception that gets generated from the assertion failure. In your catch block you could output the data however you wanted to wherever. Then when you were done you could rethrow the exception. The test runner probably wouldn't know the difference.
Disclaimer: I haven't tried this with Python's unit test framework, but I have with other unit test frameworks.
Expanding on Facundo Casco's answer, this works quite well for me:
class MyTest(unittest.TestCase):
def messenger(self, message):
try:
self.assertEqual(1, 2, msg=message)
except AssertionError as e:
print "\nMESSENGER OUTPUT: %s" % str(e),

Categories

Resources