Skip to main content

How I Debug

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
  • use pudb

The first two of these are reasonably self-explanatory, but I wanted to go into more detail about the last point, using pudb.

Using pudb to debug failing tests

pudb is an improvement on top of the built in pdb. Given a file:

# inside

def foo():
    print("Inside foo")
    value = bar()

def bar():
    print("Inside bar")
    return 15


we can use pdb:

$ python -m pdb
> /tmp/<module>()
-> def foo():
(Pdb) n
> /tmp/<module>()
-> def bar():
(Pdb) n
> /tmp/<module>()
-> foo()
(Pdb) s
> /tmp/
-> def foo():
(Pdb) s
> /tmp/
-> print("Inside foo")
(Pdb) n
Inside foo
> /tmp/
-> value = bar()
(Pdb) n
Inside bar
> /tmp/
-> print(value)
(Pdb) p value

Here I am stepping through the code and once I reach value = bar(), I can print value.

Starting pdb from the command line doesn’t work with tests. Instead, I insert this snippet:

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 pdb. What’s pudb?

pudb is a full-screen console debugger for Python

Example image stolen from 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 right.

As good as this package is, by default it cannot be used with pytest as it requires stdin to be passed, and stdout to be shown. By default pytest captures stdout. There are two solutions:

  1. run pytest with the --nocapture flag, but this will show all console output, or
  2. install pytest-pudb which transparently opens up pudb at your embed point.

  1. similar to another handy snippet: import IPython; IPython.embed() which embeds an IPython session in the current running script. ↩︎