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
Learn how to make your tests run quickly in my book Speed Up Your Django Tests.
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