How to use pdb.set_trace() in a Django unittest?

Brian Dant picture Brian Dant · Jun 20, 2013 · Viewed 10.8k times · Source

I want to debug a Django TestCase just like I would any other Python code: Simply call pdb.set_trace() and then drop into an interactive session. When I do that, I don't see anything, as the tests are run in a different process. I'm using django-discover-runner, but my guess is that this applies to the default Django test runner.

The question:

Is it possible to drop into a pdb session while using django-discover-runner a) on every error / fail, AND/OR b) only when I call pdb.set_trace() in my test code?

Some research:

This answer explains that Django creates another process, and suggests using a call to rpdb2 debugger, a part of winpdb, but I don't want to use winpdb, I'd rather use ipdb.

This answer solves the problem for django-nose by running the test command like this: ./manage.py test -- -s, but that option's not available for django-discover-runner.

This answer shows that I can do this with ipython:

In [9]: %pdb
Automatic pdb calling has been turned ON

That seems like a potential option, but it seems a bit cumbersome to fire up ipython every time I run tests.

Finally, this answer shows that nose comes with a --pdb flag that drops into pdb on errors, which is what I want. Is my only option to switch to the django-nose test runner?

I don't see any options for this in the built-in help for django-discover-runner:

$ python manage.py help test --settings=settings.test
Usage: manage.py test [options] [appname ...]

Runs the test suite for the specified applications, or the entire site if no apps are specified.

Options:
  -v VERBOSITY, --verbosity=VERBOSITY
                        Verbosity level; 0=minimal output, 1=normal output,
                        2=verbose output, 3=very verbose output
  --settings=SETTINGS   The Python path to a settings module, e.g.
                        "myproject.settings.main". If this isn't provided, the
                        DJANGO_SETTINGS_MODULE environment variable will be
                        used.
  --pythonpath=PYTHONPATH
                        A directory to add to the Python path, e.g.
                        "/home/djangoprojects/myproject".
  --traceback           Print traceback on exception
  --noinput             Tells Django to NOT prompt the user for input of any
                        kind.
  --failfast            Tells Django to stop running the test suite after
                        first failed test.
  --testrunner=TESTRUNNER
                        Tells Django to use specified test runner class
                        instead of the one specified by the TEST_RUNNER
                        setting.
  --liveserver=LIVESERVER
                        Overrides the default address where the live server
                        (used with LiveServerTestCase) is expected to run
                        from. The default value is localhost:8081.
  -t TOP_LEVEL, --top-level-directory=TOP_LEVEL
                        Top level of project for unittest discovery.
  -p PATTERN, --pattern=PATTERN
                        The test matching pattern. Defaults to test*.py.
  --version             show program's version number and exit
  -h, --help            show this help message and exit

Answer

Carl Meyer picture Carl Meyer · Jun 21, 2013

Django does not run tests in a separate process; the linked answer claiming it does is simply wrong. (The closest is the LiveServerTestCase for Selenium tests, which starts up a separate thread to run the development server, but this is still not a separate process, and it doesn't prevent use of pdb). You should be able to insert import pdb; pdb.set_trace() anywhere in a test (or in the tested code) and get a usable pdb prompt. I've never had trouble with this, and I just verified it again in a fresh project with Django 1.5.1 and django-discover-runner 1.0. If this isn't working for you, it's due to something else in your project, not due to Django or django-discover-runner.

Nose captures all output by default, which breaks import pdb; pdb.set_trace(). The -s option turns off output capturing. This is not necessary with the stock Django test runner or django-discover-runner, since neither of them do output-capturing to begin with.

I don't know of any equivalent to nose's --pdb option if you're using django-discover-runner. There is a django-pdb project that provides this, but a quick perusal of its code suggests to me that it wouldn't play well with django-discover-runner; its code might give you some clues towards implementing this yourself, though.

FWIW, personally I use py.test with pytest-django rather than django-discover-runner or django-nose. And even though py.test provides a --pdb option like nose, I don't use it; I often want to break earlier than the actual point of error in order to step through execution prior to the error, so I usually just insert import pytest; pytest.set_trace() (importing set_trace from pytest does the equivalent of nose's -s option; it turns off py.test's output capturing before running pdb) where I want it in the code and then remove it when I'm done. I don't find this onerous; YMMV.