Better Exception Output in Django’s Test Runner With better-exceptions
Today I learned about the better-exceptions pacakage. It makes exception output better, providing more context and colourization on the terminal.
If you’re using Django’s test framework, you can install better-exceptions during your test runs. It makes it the plain assert
statement much more usable.
Plain assert
s are clearer to write and read than the various self.assert*
functions, so a definite win for tests. pytest’s assert
statement rewriting is similar to better-exceptions, and it’s definitely a “killer feature” for pytest users. Whilst I recommend pytest, it can be hard to port existing projects, so using better-exceptions is a nice compromise.
Adding better-exceptions To Django Test Runs
First, you’ll want a custom test runner class. If you don’t already have one, create one as below, in a file like example/test.py
. Inside that the test runner’s run_tests()
method, you can use a monkey-patch to install better-exceptions
into the unittest TestResult
class, which is responsible for output of tests. There’s a snippet in the better-exceptions documentation, which I’ve made Python-3-only. Putting it all together:
from unittest.result import TestResult
import better_exceptions
from django.test.runner import DiscoverRunner
class ExampleTestRunner(DiscoverRunner):
def run_tests(self, *args, **kwargs):
# Enable better-exceptions for better display of exceptions
# https://github.com/Qix-/better-exceptions#use-with-unittest
def exc_info_to_string(self, err, test):
return "".join(better_exceptions.format_exception(*err))
TestResult._exc_info_to_string = exc_info_to_string
super().run_tests(*args, **kwargs)
Second, configure Django to use your test runner by setting TEST_RUNNER
:
TEST_RUNNER = "example.test.ExampleTestRunner"
Then when you run tests, you should see nice colourized output. For example I made this broken test:
from django.test import SimpleTestCase
class BrokenTests(SimpleTestCase):
def test_unequal(self):
total = 1 + 1
expected = 3
assert total == expected
Running it I see this output, with better colourization in my terminal:
$ python manage.py test
System check identified no issues (0 silenced).
F
======================================================================
FAIL: test_unequal (example.core.tests.BrokenTests)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/.../python3.9/unittest/case.py", line 59, in testPartExecutor
yield
File "/.../python3.9/unittest/case.py", line 593, in run
self._callTestMethod(testMethod)
│ └ <bound method BrokenTests.test_unequal of <example.core.tests.BrokenTests testMethod=test_unequal>>
└ <example.core.tests.BrokenTests testMethod=test_unequal>
File "/.../python3.9/unittest/case.py", line 550, in _callTestMethod
method()
└ <bound method BrokenTests.test_unequal of <example.core.tests.BrokenTests testMethod=test_unequal>>
File "/.../example/core/tests.py", line 8, in test_unequal
assert total == expected
│ └ 3
└ 2
AssertionError: assert total == expected
----------------------------------------------------------------------
Ran 1 test in 0.049s
FAILED (failures=1)
Note the last frame, which shows that total
is 2
and expected
is 3.
Fin
If you like better-exceptions, also check out its documentation on use in your Django logging configuration.
May your tests be easy to read and write,
—Adam
Newly updated: my book Boost Your Django DX now covers Django 5.0 and Python 3.12.
One summary email a week, no spam, I pinky promise.
Related posts:
- Make Django Tests Always Rebuild the Database if It Exists
- How to Use Django’s Parallel Testing on macOS With Python 3.8+
- How to Unit Test a Django Form
Tags: django