114

I am using py.test for unit testing my python program. I wish to debug my test code with the python debugger the normal way (by which I mean pdb.set_trace() in the code) but I can't make it work.

Putting pdb.set_trace() in the code doesn't work (raises IOError: reading from stdin while output is captured). I have also tried running py.test with the option --pdb but that doesn't seem to do the trick if I want to explore what happens before my assertion. It breaks when an assertion fails, and moving on from that line means terminating the program.

Does anyone know a way to get debugging, or is debugging and py.test just not meant to be together?

Vini.g.fer
  • 11,639
  • 16
  • 61
  • 90
Joel
  • 3,227
  • 5
  • 21
  • 16

7 Answers7

172

it's real simple: put an assert 0 where you want to start debugging in your code and run your tests with:

py.test --pdb 

done :)

Alternatively, if you are using pytest-2.0.1 or above, there also is the pytest.set_trace() helper which you can put anywhere in your test code. Here are the docs. It will take care to internally disable capturing before sending you to the pdb debugger command-line.

hpk42
  • 21,501
  • 4
  • 47
  • 53
  • This is great if you want to only want to debug what happens up to the assert. It would also be nice if there was a py.test.set_trace() (which would re-enable the stdin and invoke pdb.set_trace() (or similar). The problem with assert 0 is that it's not possible (AFAIK) to suppress the exception and proceed to work with the debugger. A method that invokes the debugger that doesn't alter the logic flow would be preferable. – Jason R. Coombs Aug 05 '10 at 19:39
  • Hey Jason. I see and agree. There should be a "py.test.pdb()" or something similar that just works, with or without "-s". If you file an issue, i'll see to go for implementing it. – hpk42 Sep 03 '10 at 14:38
  • 5
    Hey Jason. I've just added py.test.set_trace() , see http://bitbucket.org/hpk42/py-trunk/changeset/1d7b0838917f and you might be able to install the development version with e.g. "pip install URL" where URL is the zip file here: http://hudson.testrun.org/view/pytest/job/py-trunk-sdist/ (sorry, seems StackOverflow truncates URLs badly, so can't paste the full URL) – hpk42 Oct 06 '10 at 12:53
  • 1
    @Sardathrion you can install `pdbpp` for a better experience. – Peque Jul 11 '18 at 10:54
  • 1
    @Peque As it happens in the time since then I have moved to `pdbpp` as the experience is indeed better. ☺ – Sardathrion - against SE abuse Jul 11 '18 at 10:59
  • Also, if you want to use interactive debugger like `ipdb` in `pytest`, you can use this command: `pytest --pdbcls=IPython.terminal.debugger:TerminalPdb --pdb` – aash Dec 30 '19 at 04:36
46

I found that I can run py.test with capture disabled, then use pdb.set_trace() as usual.

> py.test --capture=no
============================= test session starts ==============================
platform linux2 -- Python 2.5.2 -- pytest-1.3.3
test path 1: project/lib/test/test_facet.py

project/lib/test/test_facet.py ...> /home/jaraco/projects/project/lib/functions.py(158)do_something()
-> code_about_to_run('')
(Pdb)
Jason R. Coombs
  • 41,115
  • 10
  • 83
  • 93
  • 4
    this is exactly how I set pdb (rather than --pdb). Note: you can use "-s" instead of "--capture=no" – alfredodeza Mar 02 '11 at 01:20
  • 1
    For many years now, it's no longer necessary to disable capture. Just add your `pdb.set_trace()` statement somewhere in the run stack. – Jason R. Coombs Aug 10 '16 at 18:40
  • 1
    @JasonR.Coombs However `ipdb.set_trace()` doesn't work or the more general `breakpoint()` which may point to it. Using `-s` still works. – user2561747 Feb 01 '19 at 23:22
  • 1
    This doesn't appear to work any more. Adding a regular `pdb.set_trace()` somewhere in the test code and then running with `-s` or `--capture=no` just runs pytest as usual, prints a summary of the tests, and never enters the debugger. – ely May 10 '19 at 15:34
  • @JasonR.Coombs My experience is that if I don't pass `-s`, pytest will fail every test with `OSError: pytest: reading from stdin while output is captured! Consider using `-s`.`. – gerrit Oct 07 '20 at 10:24
31

The easiest way is using the py.test mechanism to create breakpoint

http://pytest.org/latest/usage.html#setting-a-breakpoint-aka-set-trace

import pytest
def test_function():
    ...
    pytest.set_trace()    # invoke PDB debugger and tracing

Or if you want pytest's debugger as a one-liner, change your import pdb; pdb.set_trace() into import pytest; pytest.set_trace()

TheGrimmScientist
  • 2,812
  • 1
  • 27
  • 25
Rachid
  • 2,463
  • 1
  • 21
  • 9
  • small edit needed for ```import ipdb;ipdb.set_trace()``` – Michael May 09 '16 at 19:43
  • 1
    a lot of good answers here, but this is my preferred one. Add `pytest.set_trace()` in the function and then run `pytest --pdb ...`. Works nicely! – awerchniak Aug 17 '22 at 20:49
5

Similar to Peter Lyon's answer, but with the exact code you need for pytest, you can add the following to the bottom of your pytest module (my_test_module.py) :

if __name__ == "__main__":
    pytest.main(["my_test_module.py", "-s"])

Then you can invoke the debugger from the command line:

pdb3 my_test_module.py

Boom. You're in the debugger and able to enter debugger commands. This method leaves your test code un-littered with set_trace() calls and will run inside pytest 'normally'.

GSP
  • 93
  • 2
  • 7
4

I'm not familiar with py.test, but for unittest, you do the following. Maybe py.test is similar:

In your test module (mytestmodule.py):

if __name__ == "__main__":
    unittest.main(module="mytestmodule")

Then run the test with

python -m pdb mytestmodule.py

You will get an interactive pdb shell.

Looking at the docs, it looks like py.test has a --pdb command line option:

https://docs.pytest.org/en/7.2.x/reference/reference.html#command-line-flags

Peter Lyons
  • 142,938
  • 30
  • 279
  • 274
  • Peter, thank you for your suggestions. The python -m pdb alternative walks me through the script but doesn't call the functions, so although useful for my Python development, I don't see how I can make that one work. I mentioned the --pdb option but unless someone can think of how to use it, I can't go further with that one either. – Joel Apr 21 '10 at 19:39
  • I suggest reading the manual on pdb and learning the keystrokes. pdb will print the function that will execute next. If you type "s", you will step into that function. If you type "n", which is the default, you just go to the next line of code. It is very likely that you are hitting "n" instead of "s" when you want to step into your "main" routine. – Peter Lyons Apr 22 '10 at 14:34
  • 2
    That's not how py.test works. Small but valid test code for py.test: def test_arithmetic: assert 2+2==4 That's enough to have a unit test. No "main" routine, just a function that starts with "test_". If run this with normal python, it would just define test_arithmetic, not run it. But py.test finds functions starting with test_ and runs them for me, taking care of failed asserts etc. – Joel Apr 22 '10 at 14:55
  • dead link alert. – KansaiRobot Oct 25 '22 at 07:26
  • Fixed the link to pytest command line documentation. – Peter Lyons Nov 01 '22 at 16:51
4

Simply use: pytest --trace test_your_test.py. This will invoke the Python debugger at the start of the test

Arthur Genet
  • 51
  • 1
  • 2
2

Add and remove breakpoints without editing source files

Although you can add breakpoints by adding breakpoint() or set_trace() statements to your code, there are two issues with this approach:

  • Firstly, once you have started running your code, there is no way to remove your breakpoint. I often find that once I start running my code and reach an initial breakpoint, I want to place another one and remove the initial breakpoint. After breakpoint() drops me into the debugger I can add additional breakpoints, but I can't remove the initial one. Although this can be mitigated somewhat by putting the initial breakpoint statement higher up, if you have parametrised tests then even that is limited. I may find myself repeating cont very often.
  • Secondly, it requires changes to the source code. You need to remember to remove all breakpoint() commands before committing any code to version control, you have to remove them before switching branches, etc. I sometimes find I want to use the debugger to compare test runs between two branches, and having to edit the source code to add a breakpoint every time makes that a considerably slower and more error-prone exercise. I may even want to add a breakpoint in a library I'm calling, in which case the file I'm editing may not even me in my git repository but somewhere deep in my conda environment, increasing the risk of forgetting to remove it. Editing files to add break points is, in my humble opinion, ugly.

To add and remove breakpoints interactively without editing any source files, you can evoke pytest as follows (in the bash shell):

python -mipdb $(type -p pytest) -s test_fileset.py

The -s flag is crucial here, because it stops pytest from messing with stdin and stdout, and when running inside the debugger, pytest will fail to mess with stdin and stdout and everything will go wrong. The exact calling syntax will be different for different shells.

gerrit
  • 24,025
  • 17
  • 97
  • 170