Tests ensure that our code does what we expect it to do. They also ensure that changes to the codebase don’t break something unexpected, and they signal to other users of our app that they can depend on the code.
In this chapter, you’ll learn exactly how tests provide value in a standalone Django app, how to run your app’s tests outside of a Django project, how to test more complicated multi-app relationships, how to include your tests, and also whether or not you need to configure Django in order to test your app.
Why test?
Everyone says that you should test. It sounds obvious - if testing is good, we should do it. But this begs the question about the benefits of testing.
Testing your Django app several purposes. Written in conjunction with, or before your application code, tests helps provide a working specification against which your code can be verified. In this capacity they can also help shape the code and interface, as if you’re adding some feature from scratch, a test will give you your first chance of using it.
Once in place, even otherwise trivial tests serve to protect against regressions introduced by seemingly trivial changes to the codebase.
While not their primary use, tests can also provide an example of how to use your code. In this capacity they’re certainly not a replacement for proper documentation, but tests as code examples - especially when tests are run automatically - are a form of documentation that you can verify is up to date.
Underlying all of this is the fact that computer programs are written by human beings and we humans are terribly unreliable when it comes to writing reliable code on our own (apologies if this does not apply to you). There are all kinds of things we can’t predict, edge cases we’re not good at seeing right away, and interactions that aren’t obvious at the surface of our code.
Testing doesn’t solve all of these problems, but tests provide a potent tool to remove a lot of uncertainty about our code. Ultimately tests provide confidence, both for you and other users of your app - and don’t forget that “future you” is likely one of those users!
Testing apps from a Django project
then the command python manage.py test myapp will run all of the test in myapp.tests using Django’s default test runner, for example, with the example tests file given, the command will run the TestSomeModel.test_str_method.
This works just fine when you’re working from a larger Django project, for example, if you’re developing your app in the context of a working project. It’s of much less help if your app is a standalone library where the code is intended to be managed from outside of a project. For a standalone app, it’d be much preferable to be able to run tests just like any other Python package.
Testing the app
If you’ve worked with other Python packages before, you’ll have noticed that they’re tested in a straightforward way. There’ll be a test module somewhere and usually the setup.py file defines a test script to run using the python setup.py test command. That works for packages using Django, too, with the caveat that much Django functionality must be run from the context of a Django project, something Python’s unittest won’t take care of for you.
To motivate some reasonable ways of testing a standalone app, let’s consider the most immediately available strategy for testing the app: testing from whatever project you’re using the app in (presuming you are extracting it).
This means that to test the myapp app, it needs to be installed on the same path as your working project, that is, the same virtual environment, and that it needs to be in your working project’s INSTALLED_APPS. When it’s time to test changes to myapp, you’ll need to go back to the working project to run them, that is, running ./manage.py test myapp.
If this sounds less than sensible, you’re on the right track. However, this strategy doesn’t allow testing a standalone app, which means it’s not repeatable for anyone else who isn’t working with your project. And if you’re going to package the app for reuse, you won’t have recourse to your original project. Thankfully there’s a better way.
Testing outside of a project
To motivate our subsequent solutions, we’ll set up the most obvious solution possible. This will entail setting up a dummy, or holder, project and running the tests from there. To do, we would create new Django project in our app’s root folder, parallel to the app source folder itself. This project will then include our app in the INSTALLED_APPS list.
Then, running the tests and any other commands is as simple as invoking the holder project’s manage.py file just like any other project.
This works and is an improvement over the original example, but it still adds more than necessary just to run our tests.
Using a testing script
Of course, Django doesn’t demand that we have project scaffolding, just that Django settings are configured. So a better solution is a Python script that configures those minimalist settings and then runs the tests.
- 1.
Define or configure Django settings
- 2.
Trigger Django initialization (i.e., with django.setup())
- 3.
Execute the test runner
Why choose one over the other? Using a separate settings module will be more flexible if you have other needs for the settings. The in-script configuration style suffices for simpler apps, for example, those without models.
In Chapter 22 we’ll examine a more ergonomic way of managing your tests and test configuration.
Testing application relationships
What happens though when your Django app is designed to be used with other apps, or used in conjunction with them? Testing only your app in isolation is not enough. In this case you’ll need to create sample apps and include them in your test settings.
Now to test this, we need a concrete model (and it would be helpful to have tests actually using the base model anyhow). To do this we’ll need another app that defines a concrete model inheriting from our abstract model, and that uses the provided queryset.
Note that this applies to Django packages that are not installable apps as well if they require any level of integration testing.
Where to include tests
Testing without Django
The emphasis here is on Django apps, that is, Python modules that can be installed and included in a Django project to use models, template tags, management commands, among others. But in many cases the functionality provided by apps can be tested as plain old Python code.
This will be the case with anything in your app that requires setup, like models. However, this isn’t true of every part of Django or every part of your app. And in fact if your app doesn’t have any models, and you don’t have any request-related functionality to test - especially at an integration test level - then you can forgo with setting up or using Django’s test modules, sticking to the standard library’s unittest, or any other testing framework you so choose.
You will only need to invoke a test runner through Django if you’re loading the Django project, for example, anything involving models, settings, or a full request/response cycle. In most cases, testing features like forms, the logic in template tags and filters and others, is not dependent on any of the parts of Django that require project setup.
Why would you do this? It’s extraordinarily doubtful that the performance gains from using unittest over django.test are going to be noticeable to say nothing of impactful. However, if these are the only tests that you need, then your testing environment will be simpler to set up and run.
Summary
In this chapter, you learned why it’s important to have tests for your standalone app and how to begin testing a Django app when it’s no longer part of a parent Django project. You also learned how to simplify test execution with a Python script that handles Django setup and how to test app features that are predicated on relationships defined by other apps outside of your own. Lastly, you learned where to include the tests for you standalone app, in a top-level tests directory, and that, for some types of apps that don’t rely on the database or template engine, it may be sufficient to use Python’s unittest library without the Django setup.
In the next chapter, you’ll learn how to manage database migrations for your app without a Django project.