You can drop right into a pdb session when a pytest test fails, which is way better than staring at a traceback.

Here’s a simple test file that will fail:

# test_example.py
def test_addition():
    a = 5
    b = 10
    assert a + b == 20 # This will fail

Now, run pytest with the --pdb flag:

pytest --pdb test_example.py

When the AssertionError happens, you’ll see a (Pdb) prompt. You can now inspect variables, step through code, and figure out what went wrong.

Let’s see what happens when the test fails. Pytest will stop execution right at the line that caused the failure, and you’ll be presented with the (Pdb) prompt.

============================= test session starts ==============================
platform linux -- Python 3.10.12, pytest-7.4.0, pluggy-1.2.0
rootdir: /home/user/project
collected 1 item

test_example.py F                                                         [100%]

=================================== FAILURES ===================================
_________________________________ test_addition __________________________________

    def test_addition():
        a = 5
        b = 10
>       assert a + b == 20 # This will fail
E       assert 15 == 20

test_example.py:5: AssertionError
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> Pdb Post Mortem >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
*** Regression test for pdb post mortem ***
> /home/user/project/test_example.py(5)test_addition()
-> assert a + b == 20 # This will fail
(Pdb)

At the (Pdb) prompt, you can use standard pdb commands:

  • p <variable_name>: Print the value of a variable. For example, p a will show 5. p b will show 10. p a + b will show 15.
  • l: List the source code around the current line.
  • n: Execute the next line.
  • c: Continue execution (which will likely lead to the test finishing its failure).
  • q: Quit the debugger.

This lets you interactively explore the state of your program exactly when and where it failed. You’re not just reading a static traceback; you’re in the code.

The real power comes when you have more complex setup or when the failure is not immediately obvious. Imagine a test that fails deep within a function call chain. With --pdb, you can jump in and see the values of all the arguments passed down the line, the intermediate results, and the final outcome.

For instance, if test_addition was calling another function:

# test_example_complex.py
def helper_function(x, y):
    result = x * y
    return result

def test_complex_calculation():
    val1 = 7
    val2 = 8
    expected = 60
    actual = helper_function(val1, val2)
    assert actual == expected # This will fail

Running pytest --pdb test_example_complex.py would drop you into pdb at assert actual == expected. You could then use p val1, p val2, p actual to see that actual is 56, which is not 60. If helper_function was more complex, you could even step into it using s (step) if the pdb session started before the call to helper_function (which --pdb doesn’t do by default, it stops after the exception).

The --pdb flag is a shortcut for pytest’s internal set_trace() functionality, but it’s automatically triggered on assertion failures. If you wanted to break at a specific point before a failure, you’d explicitly add import pdb; pdb.set_trace() in your test code. However, --pdb is fantastic for those "what the heck just happened?" moments.

The core mechanism is that pytest catches the exception, and then it invokes the pdb debugger in post-mortem mode. This means it’s not just stopping execution; it’s providing you with a debugger that has access to the entire call stack and local variables as they were when the exception occurred.

When you’re done debugging and want to exit pdb, typing q will typically quit the debugger and pytest will then report the failure. If you were to fix the assertion assert a + b == 15, the test would then pass.

The next thing you’ll run into is needing to debug tests that don’t fail with an AssertionError but raise a different exception, like TypeError or KeyError.

Want structured learning?

Take the full Pytest course →