J
Jens Theisen
Hello,
I find it annoying that one has to write
self.assertEqual(x, y)
rather than just
assert x == y
when writing tests. This is a nuisance in all the programming
languages I know of (which are not too many). In Python however, there
appears to be a better alternative. The piece of code below gives the
benefit of printing the violating values in case of a testing failure
as well as the concise syntax:
The snippet
def test_test():
def foo(x):
return x + 3
x = 1
y = 2
assert foo(x) < y + x
try:
test_test()
except AssertionError:
analyse()
would give:
Traceback (most recent call last):
File "./ast-post.py", line 138, in ?
test_test()
File "./ast-post.py", line 134, in test_test
assert foo(x) < y + x
AssertionError
failure analysis:
foo: <function foo at 0xb7c9148c>
x: 1
( x ): 1
foo ( x ): 4
y: 2
x: 1
y + x: 3
foo ( x ) < y + x: False
The code that makes this possible relies only on code present in the
standard library (being traceback, inspect and the parsing stuff)
while being as short as:
#!/usr/bin/python
import sys, types
import traceback, inspect
import parser, symbol, token
import StringIO
def get_inner_frame(tb):
while tb.tb_next:
tb = tb.tb_next
return tb.tb_frame
def visit_ast(visitor, ast):
sym = ast[0]
vals = ast[1:]
assert len(vals) > 0
is_simple = len(vals) == 1
is_leaf = is_simple and type(vals[0]) != types.TupleType
if not is_leaf:
visitor.enter()
for val in vals:
visit_ast(visitor, val)
visitor.leave()
if is_leaf:
visitor.leaf(sym, vals[0])
elif is_simple:
visitor.simple(sym, vals[0])
else:
visitor.compound(sym, vals)
class ast_visitor:
def enter(self):
pass
def leave(self):
pass
def leaf(self, sym, val):
pass
def simple(self, sym, val):
pass
def compound(self, sym, vals):
pass
class simple_printer(ast_visitor):
def __init__(self, stream):
self.stream = stream
def leaf(self, sym, val):
print >>self.stream, val,
def str_from_ast(ast):
s = StringIO.StringIO()
visit_ast(simple_printer(s), ast)
return s.getvalue()
class assertion_collector(ast_visitor):
def __init__(self, statements):
self.statements = statements
def compound(self, sym, vals):
if sym == symbol.assert_stmt:
# two nodes: the "assert" name and the expression
self.statements.append(vals[1])
class pretty_evaluate(ast_visitor):
def __init__(self, globals_, locals_):
self.globals = globals_
self.locals = locals_
def _expr(self, expression):
code = compile(expression, '<internal>', 'eval')
try:
result = eval(code, self.globals, self.locals)
except Exception, e:
result = e
print '%50s: %s' % (expression, str(result))
def compound(self, sym, vals):
ast = [ sym ]
ast.extend(vals)
expression = str_from_ast(ast)
self._expr(expression)
def leaf(self, sym, val):
if sym == token.NAME:
self._expr(val)
def analyse():
type_, exc, tb = sys.exc_info()
frame = get_inner_frame(tb)
try:
filename, line, fun, context, index = (
inspect.getframeinfo(frame, 1)
)
ast = parser.suite(context[0].lstrip()).totuple()
assert_statements = [ ]
visit_ast(assertion_collector(assert_statements), ast)
traceback.print_exc()
print "\nfailure analysis:\n"
for statement in assert_statements:
visit_ast(
pretty_evaluate(frame.f_globals, frame.f_locals), statement)
finally:
del frame
I find it annoying that one has to write
self.assertEqual(x, y)
rather than just
assert x == y
when writing tests. This is a nuisance in all the programming
languages I know of (which are not too many). In Python however, there
appears to be a better alternative. The piece of code below gives the
benefit of printing the violating values in case of a testing failure
as well as the concise syntax:
The snippet
def test_test():
def foo(x):
return x + 3
x = 1
y = 2
assert foo(x) < y + x
try:
test_test()
except AssertionError:
analyse()
would give:
Traceback (most recent call last):
File "./ast-post.py", line 138, in ?
test_test()
File "./ast-post.py", line 134, in test_test
assert foo(x) < y + x
AssertionError
failure analysis:
foo: <function foo at 0xb7c9148c>
x: 1
( x ): 1
foo ( x ): 4
y: 2
x: 1
y + x: 3
foo ( x ) < y + x: False
The code that makes this possible relies only on code present in the
standard library (being traceback, inspect and the parsing stuff)
while being as short as:
#!/usr/bin/python
import sys, types
import traceback, inspect
import parser, symbol, token
import StringIO
def get_inner_frame(tb):
while tb.tb_next:
tb = tb.tb_next
return tb.tb_frame
def visit_ast(visitor, ast):
sym = ast[0]
vals = ast[1:]
assert len(vals) > 0
is_simple = len(vals) == 1
is_leaf = is_simple and type(vals[0]) != types.TupleType
if not is_leaf:
visitor.enter()
for val in vals:
visit_ast(visitor, val)
visitor.leave()
if is_leaf:
visitor.leaf(sym, vals[0])
elif is_simple:
visitor.simple(sym, vals[0])
else:
visitor.compound(sym, vals)
class ast_visitor:
def enter(self):
pass
def leave(self):
pass
def leaf(self, sym, val):
pass
def simple(self, sym, val):
pass
def compound(self, sym, vals):
pass
class simple_printer(ast_visitor):
def __init__(self, stream):
self.stream = stream
def leaf(self, sym, val):
print >>self.stream, val,
def str_from_ast(ast):
s = StringIO.StringIO()
visit_ast(simple_printer(s), ast)
return s.getvalue()
class assertion_collector(ast_visitor):
def __init__(self, statements):
self.statements = statements
def compound(self, sym, vals):
if sym == symbol.assert_stmt:
# two nodes: the "assert" name and the expression
self.statements.append(vals[1])
class pretty_evaluate(ast_visitor):
def __init__(self, globals_, locals_):
self.globals = globals_
self.locals = locals_
def _expr(self, expression):
code = compile(expression, '<internal>', 'eval')
try:
result = eval(code, self.globals, self.locals)
except Exception, e:
result = e
print '%50s: %s' % (expression, str(result))
def compound(self, sym, vals):
ast = [ sym ]
ast.extend(vals)
expression = str_from_ast(ast)
self._expr(expression)
def leaf(self, sym, val):
if sym == token.NAME:
self._expr(val)
def analyse():
type_, exc, tb = sys.exc_info()
frame = get_inner_frame(tb)
try:
filename, line, fun, context, index = (
inspect.getframeinfo(frame, 1)
)
ast = parser.suite(context[0].lstrip()).totuple()
assert_statements = [ ]
visit_ast(assertion_collector(assert_statements), ast)
traceback.print_exc()
print "\nfailure analysis:\n"
for statement in assert_statements:
visit_ast(
pretty_evaluate(frame.f_globals, frame.f_locals), statement)
finally:
del frame