In November 2016, I had the great opportunity of travelling to Amsterdam and attending Django: Under the Hood ✨ for the first time. This is an important conference for Django developers and provides deep and detailed talks. It’s a great opportunity to find out what is happening, learn about the future plans for Django and to meet a lot of people from the Django community—most of them from the Django Core Team. ❤
I attended the talks and the first day of Sprints. But today, I’m here to highlight some points — and tools — from one of the interesting talks by Ana Balica: Testing in Django. But more than that, I want to first start by talking about Tests — though not only in Django — and why you should…
I’ve been a Backend Developer at Kiwi.com working on a project in Django for almost a year. It’s the company’s first project in Django and to be honest we had our first test a few months after I started here. We’re about to celebrate our 500th test! ???? Even though, for me, this number of itself doesn’t mean anything. ????
There are many types of tests but the the two main ones are:
- Unit Tests: test highly-specific parts of code — a specific function, for example.
- Integration Tests: test small parts combined together to ensure the behaviour of the whole functionality is correct.
Why should I test?
Why shouldn’t you? ???? When it comes to testing, never be that confident.
We all know that tests are important. But most people don’t take a few hours out of their work — no-time-for-testing — day to write tests properly because “it takes time” and there’s always something else more important to start/finish. So, tests became a low priority.
Can you think how much time it takes when you’re coding new functionality to manually test it for each change — and each scenario? It’s probably similar to or more than the time you spend writing simple tests: like a unit test.
When it comes to integration testing, it may take more time to write tests because the business logic is usually more complicated. However, if you don’t spend any time testing at all, soon enough, you’ll spend much more time finding and fixing those nasty bugs.
So, “it takes time” is not a real reason for not having tests.
What about importance? Is it more important to guarantee that the last feature is working properly, or to start a new task and return to the previous one after days — or weeks — because there is a bug you didn’t catch: simply because you don’t have tests?
What should I test?
Let’s start with what you shouldn’t test.
- Built-in functions/libraries — for example,
datetimelibrary in Python.
- Code built into the Framework — for example,
Modelfields in Django.
- For everything else, you should have tests. All business logic — any custom methods for models, views, forms, template tags, middleware, management commands, context processors, and so on.
When should I test?
- Test-After: write the tests after you’ve already written the code for new features, fixes, etc.
- Test-First (TDD — Test Driven Development): at the beginning it could seem a boring-repetitive-waste-of-time to write the tests step by step, plus we’re developers and all we want is to code cool new features the whole day ???? — not to test. If there’s something I learned from the Welcome to the Django course — by Henrique Bastos — it’s Test First! The entire course is based on TDD and it was a great experience that hopefully soon I’ll apply to my daily work and personal projects. ???? Why don’t you give it a try? ???? At least you’ll know if it doesn’t work for you.
From DUTH 2016: Testing in Django
Finally, I want to highlight some points and a few libraries mentioned in Ana’s talk.
Presentation Testing in Django by Ana Balica
Factory Boy is a fixtures replacement tool; in other words, it allows the replacement of the static and hard to maintain data for random generated inputs.
Factory Boy + Faker
Even better than generating random values is generating realist values. To be able to do that, Factory Boy counts on the fake-factory library. The faker generator contains
providers which generate certain types of data.
In the example below,
faker.name() yields a different random name for each iteration.
Factory Boy already works with Faker and you can generate
name() , for example, by using
factory.Faker('name'). So, when using Factory Boy + Faker in the example above, we would have:
Cleaner, right? ???????? You don’t need to create a separate method to generate the data: Faker does this for you!
A new level for testing which randomises the inputs; Hypothesis is a Python library that runs the test multiple times against wider scenarios.
It uses the decorator
@given() to inject random data into the test based on distinct strategies — which are comparable to Factory Boy’s or Fake’s providers.
You can find an extra module called
django with full support for Django. An example of Django
TestCase is presented by the Hypothesis guy:
It’s worth mentioning that Hypothesis will always save the created model.
Test Code Coverage
High Coverage ≠ High Quality
It’s important to emphasise this from Ana’s talk. When we have low coverage, it means that we don’t have many tests written. If the coverage is high it doesn’t mean anything, except that we reached a certain percentage of the lines of code at least once — even for more complicated/complex coding.
When testing, more is better.
But it’s also important to know how and what you should test. Also remember to refactor/remove the unnecessary tests, or those that are not being used anymore. It’s not only about writing more tests, but keeping the consistency of how everything should work. ✓
MutPy is a mutation test tool. It provides small alterations on the source code or byte code with the purpose of removing redundancies and helping the developer find possible weaknesses in the code.
Example extracted by MutPy.
Suppose you want to test the method
def mul(x, y) from
calculator.py. The test case for our calculator is presented below:
Then, run MutPy:
The output should be something like this:
It gives the total mutants (
all: 4), how many mutants were killed by the test ???????? (
killed: 3), how many survived ???????? (
survived: 1), incompetent and timeout.
It’s a simple case to demonstrate how a mutant can be used. You can check this whole list of mutation operators.
Top 3 tips for speeding up your tests:
- Be vigilant of what gets created in
setUp(): identify, remove and/or reduce the usage of unnecessary code — such as the creation of objects in loops — as much as possible.
- Don’t save model objects if not necessary: use the in-memory model where possible — such as
Employee.objects.create(). Also, using Factory Boy, you can use
- Isolate unit tests: unit tests are faster and can be run very often. ???????? The tip here is to write more simple unit tests or pytest because they have no idea what Django is.
Ana provided an interesting example. I’ve implemented it and it’s available on my GitHub.
Let’s say we have a Bakery and we would like calculate the correct VAT (Value Added Tax) for the products. We have two models
ProductQuestionnaire has a foreign key to
Product and based on the answers of the questionnaire, we decide if the
Product should be taxed. In this small example, it needs to be a biscuit and coated in chocolate for 20% VAT to be added.
Put the logic to
To test if the product should have 20% VAT, meaning the
Product is biscuit coated in chocolate, I need to:
- Go through the router
- Interact with the database
- Send input to receive output
When moving the logic to
forms, we will be able to remove the routing, but still need to interact with the database and send an input to receive an output.
Logic on separate class.
If we move the logic to a separate class, we can precisely test only what’s necessary.
PS: All coding examples are available on my GitHub. ✨
Remember: functional tests are still really important to validate the contract between units.
There’s a LOT of things I could write about tests, but the first step is to recognise the value of having them. If you still don’t have/write tests, start from the beginning, step-by-step, with simple unit tests. After all, it’s better having simple tests than no tests at all. ???????? As time goes on, testing will become part of your day. ✨
What do you think about tests? Let me know ✓