When we write custom management commands, it’s easy to write integration tests for them with call_command().
This allows us to invoke the management command as it runs under manage.py, and retrieve the return code, standard output, and standard error.
It’s great, but has some overhead, making our tests slower than necessary.
If we have logic separated out of the command’s handle() method, it improves both readability and testability, as we can unit test it separately.
Take this management command, that implements some book title normalization rules:
The logic inside normalize_title() has been separated from the handle() method, allowing us to use and test it in isolation.
We could write integration tests for this command that create data, run the command, and check the output was expected and the data was correctly updated:
These tests capture the command output through the stdout argument to call_command(), and then make assertions on it.
The visible repetition in writing and reading the Book instances to the database point us to some overhead.
There’s also the overhead of running call_command() to test only behaviour from normalize_title().
Seeing both of these nudges us to move the normalization tests to unit tests.
We can change some of these tests to directly test normalize_title() instead:
Because the new tests don’t touch the database, we have placed them in a SimpleTestCase class, saving some database overhead.
They’re also shorter as they don’t require any arrangement step.
There are two integration tests left, to cover the “dry run” and “write” pathways.
May you write faster, more targeted tests,
Working on a Django project?
Check out my book Speed Up Your Django Tests which covers loads of best practices so you can write faster, more accurate tests.
One summary email a week, no spam, I pinky promise.