A discussion came up today about how to debug failing tests.
My strategy typically follows this path:
- look at the stacktrace and code and try to figure it out,
- insert print statements into the code to get key values
The first two of these are reasonably self-explanatory, but I wanted to go into
more detail about the last point, using
pudb to debug failing tests
pudb is an improvement on top of the built in
pdb. Given a file:
# inside example.py def foo(): print("Inside foo") value = bar() print(value) def bar(): print("Inside bar") return 15 foo()
we can use
$ python -m pdb example.py > /tmp/example.py(1)<module>() -> def foo(): (Pdb) n > /tmp/example.py(6)<module>() -> def bar(): (Pdb) n > /tmp/example.py(10)<module>() -> foo() (Pdb) s --Call-- > /tmp/example.py(1)foo() -> def foo(): (Pdb) s > /tmp/example.py(2)foo() -> print("Inside foo") (Pdb) n Inside foo > /tmp/example.py(3)foo() -> value = bar() (Pdb) n Inside bar > /tmp/example.py(4)foo() -> print(value) (Pdb) p value 15 (Pdb)
Here I am stepping through the code and once I reach
value = bar(), I can
pdb from the command line doesn’t work with tests. Instead, I insert
import pdb; pdb.set_trace()
which opens a
pdb session at the current line.
pdb is great, but it has one major drawback: the amount of information visible
on the screen. You cannot see your code, local variables, the current stack or
information about breakpoints you have set. I said I used
pudb instead of
Image shamelessly stolen from this blog post
It opens a full-console debugger. The source code can be seen, breakpoints can be set interactively on lines, local scope can be inspected as well as the current stack position.
I am a full time vim user so I tend to use other terminal-based tools. This
debugger is perfect for those really tricky situations where interactivity is
required. A quick
Ctrl-X and the local environment can be investigated1.
For a quick glance, the current local variables can be inspected in the top
As good as this package is, by default it cannot be used with
pytest as it
stdin to be passed, and
stdout to be shown. By default
stdout. There are two solutions:
--nocaptureflag, but this will show all console output, or
pytest-pudbwhich transparently opens up
pudbat your embed point.
similar to another handy snippet:
import IPython; IPython.embed()which embeds an IPython session in the current running script. ↩︎