Pytest is a very complete test framework for Python. I like how you can write a basic unittest.TestCase and the py.test test runner command will inject all its magic at runtime, without you having to directly import anything: awesome separation of concerns.
This modularity comes at a cost though: py.test actually preprocess tests before running them, by parsing their AST tree and replacing assert calls by custom exceptions "manually "raised. The final compiled .pyc binaries are then cached in a __pycache__/ directory.
My curiosity got aroused by this blog post from 2011 : wouldn't that be nice to peek into this process and check what the modified code look like exactly ?
I considered 2 solutions:
- either decompile the cached
.pycfiles, but I couldn't feed them touncompyle2norpycdcwithout raising bytecode format errors. - take a glance at Pytest code base and find a way to invoke its custom AST-parsing method, then "AST-unparse" the resulting AST tree instead of compiling it down to bytecode.
This second solution revealed to be very easy to implement. I hesitated for a moment between two good-looking AST-unparser, namely astor and astunparse, and ended up with the following code:
from _pytest.assertion.rewrite import rewrite_asserts
import ast, astunparse, sys
with open(sys.argv[1], 'r') as open_file:
ast_tree = ast.parse(open_file.read())
rewrite_asserts(ast_tree)
print astunparse.unparse(ast_tree)
And that's it ! To test it, just write a dummy stupid_test.py file with:
def dummy_test():
assert False
And then python pytest_rewrite.py stupid_test.py :
import __builtin__ as @py_builtins
import _pytest.assertion.rewrite as @pytest_ar
def dummy_test():
if (not False):
@py_format1 = (('' + 'assert %(py0)s') % {'py0': (@pytest_ar._saferepr(False) if (('False' in @py_builtins.locals()) or @pytest_ar._should_repr_global_name(False)) else 'False')})
raise AssertionError(@pytest_ar._format_explanation(@py_format1))

Now, I want to conclude on a more nuanced tone: not everything is perfect in the Pytest world, and I have a few pain points to mention:
- Pytest terminal reports are rendered character by character, making it impossible to write log messages to stdout without messing everything
- Pytest wraps every module/class/object in your tests into custom wrappers and use generic callback hooks on at least 3 invocation levels : I had to scratch my head for some time to debug minor errors stacktraces and hack around it
- Pytest code base is very dense and not always following PEP code standards, making it very difficult to understand and contribute
- finally, I have a last minor complaint: when using
pytest-xdistto parallelize tests, you cannot write to stdout. I guess it'd be difficult to collect the standard outputs of every process spawned bypytest-xdist, but it's still a PITA.