Test Driven Development - Unit Tests
Some time ago I was working through ‘Test-Driven Development with Python’ by Harry Percival with the intention of writing up how I got on as I went. As often happens, work and life got in the way of writing, so sometime later I am recommencing my quest.
After having created a failing first minimum viable functional test for a to-do list in Test Driven Development - Meeting the Testing Goat the next step is to create a Django app and to start on unit tests.
Functional Tests and Unit Tests
The difference between functional and unit tests can get a bit blurred. In his book, Harry Percival defines the difference as functional tests, test the application from the point of view of the user, whereas unit tests test from the inside, from the point of view of the programmer. For example, a functional test might test what happens when a user clicks ‘buy item x’ on an online shop. This might involve testing many methods and other dependencies such as databases, web servers, etc. Unit tests, on the other hand, will test individual units, such as the methods that make up all the code tested in the functional test. In this way, there might be many unit tests testing the code for one functional test.
This book takes the approach of creating a failing functional test first, then writing and running failing unit tests for small parts of this functionalty before writing code to pass the unit tests. After this, run the functional test again, add any additional code needed before again, writing more failing unit tests and so on.
I don’t want to go into too much detail from the book as I might as well just be re-writing it. So, as mentioned in my first blog on TDD, I am mainly going to focus on the changes I found when using the latest version of Django. This is the first one:
The module django.core.urlresolvers is no more!
So, I am at the point where I am writing the first unit test which should be a simple example of the aim; to write a test that fails in an expected way before writing code to pass it. But sometimes there are unexpected failures like this one:
This is one of the reasons why tests are so great. The first line of the unit test imports resolve from django.core.urlresolvers but the test error is clearly showing that this module is not found:
ModuleNotFoundError: No module named ‘django.core.urlresolvers’
With a quick search on the Django documentation I find that this module was removed in Django 2.0 and its functionality moved to django.urls.
So this line needs to be replaced with:
from django.urls import resolve
Try running the unit test again:
$ python3 manage.py test
The above is the failure I expected to see as I was trying to import something that I haven’t written yet.
urls.py
So, now I want to try to get my test to pass by creating a simple view in lists/view.py.
from django.shortcuts import render
# create your view here
home_page = None
This will fail as Django hasn’t been told how to find a URL to map to the root (“/”) of the site yet. Here is the failure report.
The reason I have given the previous two steps here is that the next part is a bit different from the book. Django version 1.7 uses regular expressions (regex) for its URL mapping but they scrapped this in later versions in favour of path. So, instead of just changing the superlists/urls.py file a couple of changes are necessary:
- Create a urls.py file in lists at the same level as tests.py and add a path to the home page to it:
- As mentioned above the urls.py file in superlists/superlists won’t look the same as the book example because Django 3 doesn’t use regular expressions. You will need to do something like this:
After making the above changes and ignoring those related to regex from Django 1.7 the test can be run again and get the expected error:
Then just a quick change in lists/views.py to create a simple function for home_page before running the unit test again, this time it should pass.
Currently, there is one unit test passing and the functional test still fails. The final step in this section is to write another unit test to check that the home_page function returns a real HTTP response to the browser. After writing the new unit test and a bit of back and forth with minimal code changes to fix each failing error of the test it finally passes and when run again so does the functional test.
You can continue to follow my journey through the book on my Github repo. Next step: Chapter 4!