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 -- Cheers, Jens -- http://mail.python.org/mailman/listinfo/python-list