TDD isn’t something that comes naturally. It’s a discipline, like a martial art, and just like in a Kung-Fu movie, you need a bad-tempered and unreasonable master to force you to learn the discipline. Ours is the Testing Goat.
The Testing Goat is the unofficial mascot of TDD in the Python testing community. It probably means different things to different people, but, to me, the Testing Goat is a voice inside my head that keeps me on the True Path of Testing—like one of those little angels or demons that pop up above your shoulder in the cartoons, but with a very niche set of concerns. I hope, with this book, to install the Testing Goat inside your head too.
We’ve decided to build a website, even if we’re not quite sure what it’s going to do yet. Normally the first step in web development is getting your web framework installed and configured. Download this, install that, configure the other, run the script … but TDD requires a different mindset. When you’re doing TDD, you always have the Testing Goat inside you — single-minded as goats are—bleating “Test first, test first!”
In TDD the first step is always the same: write a test.
First we write the test, then we run it and check that it fails as expected. Only then do we go ahead and build some of our app. Repeat that to yourself in a goat-like voice. I know I do.
Another thing about goats is that they take one step at a time. That’s why they seldom fall off mountains, see, no matter how steep they are. As you can see in Figure 1-1.
The first thing we want to do is check that we’ve got Django installed, and that it’s ready for us to work with. The way we’ll check is by confirming that we can spin up Django’s development server and actually see it serving up a web page, in our web browser, on our local PC. We’ll use the Selenium browser automation tool for this.
Create a new Python file called functional_tests.py, wherever you want to keep the code for your project, and enter the following code. If you feel like making a few little goat noises as you do it, it may help:
That’s our first functional test (FT); I’ll talk more about what I mean by functional tests, and how they contrast with unit tests. For now, it’s enough to assure ourselves that we understand what it’s doing:
Let’s try running it:
$ python3 functional_tests.py Traceback (most recent call last): File "functional_tests.py", line 6, in <module> assert 'Django' in browser.title AssertionError
You should see a browser window pop up and try and open localhost:8000, and then the Python error message should appear. And then, you will probably be irritated at the fact that it left a Firefox window lying around your desktop for you to tidy up. We’ll fix that later!
If, instead, you see an error trying to import Selenium, you might need to go back and have another look at the Prerequisites and Assumptions chapter.
For now though, we have a failing test, so that means we’re allowed to start building our app.
Since you’ve definitely read Prerequisites and Assumptions by now, you’ve already got Django installed. The first step in getting Django up and running is to create a project, which will be the main container for our site. Django provides a little command-line tool for this:
$ django-admin.py startproject superlists
That will create a folder called superlists, and a set of files and subfolders inside it:
. ├── functional_tests.py └── superlists ├── manage.py └── superlists ├── __init__.py ├── settings.py ├── urls.py └── wsgi.py
Yes, there’s a folder called superlists inside a folder called superlists. It’s a bit confusing, but it’s just one of those things; there are good reasons when you look back at the history of Django. For now, the important thing to know is that the superlists/superlists folder is for stuff that applies to the whole project—like settings.py for example, which is used to store global configuration information for the site.
You’ll also have noticed manage.py. That’s Django’s Swiss Army knife, and
one of the things it can do is run a development server. Let’s try that now.
cd superlists to go into the top-level superlists folder (we’ll
work from this folder a lot) and then run:
$ python3 manage.py runserver Validating models... 0 errors found Django version 1.7, using settings 'superlists.settings' Development server is running at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
Leave that running, and open another command shell. In that, we can try running our test again (from the folder we started in):
$ python3 functional_tests.py $
Not much action on the command line, but you should notice two things: firstly,
there was no ugly
AssertionError and secondly, the Firefox window that
Selenium popped up had a different-looking page on it.
Well, it may not look like much, but that was our first ever passing test! Hooray!
If it all feels a bit too much like magic, like it wasn’t quite real, why not go and take a look at the dev server manually, by opening a web browser yourself and visiting http://localhost:8000? You should see something like Figure 1-2.
You can quit the development server now if you like, back in the original shell, using Ctrl-C.
There’s one last thing to do before we finish the chapter: start to commit our work to a version control system (VCS). If you’re an experienced programmer you don’t need to hear me preaching about version control, but if you’re new to it please believe me when I say that VCS is a must-have. As soon as your project gets to be more than a few weeks old and a few lines of code, having a tool available to look back over old versions of code, revert changes, explore new ideas safely, even just as a backup … boy. TDD goes hand in hand with version control, so I want to make sure I impart how it fits into the workflow.
So, our first commit! If anything it’s a bit late, shame on us. We’re using Git as our VCS, ‘cos it’s the best.
$ ls superlists functional_tests.py $ mv functional_tests.py superlists/ $ cd superlists $ git init . Initialised empty Git repository in /workspace/superlists/.git/
From this point onwards, the top-level superlists folder will be our working directory. Whenever I show a command to type in, it will assume we’re in this directory. Similarly, if I mention a path to a file, it will be relative to this top-level directory. So superlists/settings.py means the settings.py inside the second-level superlists. Clear as mud? If in doubt, look for manage.py; you want to be in the same directory as manage.py.
Now let’s take a look and see what files we want to commit:
$ ls db.sqlite3 manage.py superlists functional_tests.py
db.sqlite3 is a database file. We don’t want to have that in
version control, so we add it to a special file called .gitignore
which, um, tells Git what to ignore:
$ echo "db.sqlite3" >> .gitignore
Next we can add the rest of the contents of the current folder, “.”:
$ git add . $ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: functional_tests.py new file: manage.py new file: superlists/__init__.py new file: superlists/__pycache__/__init__.cpython-34.pyc new file: superlists/__pycache__/settings.cpython-34.pyc new file: superlists/__pycache__/urls.cpython-34.pyc new file: superlists/__pycache__/wsgi.cpython-34.pyc new file: superlists/settings.py new file: superlists/urls.py new file: superlists/wsgi.py
Darn! We’ve got a bunch of .pyc files in there; it’s pointless to commit those. Let’s remove them from Git and add them to .gitignore too:
$ git rm -r --cached superlists/__pycache__ rm 'superlists/__pycache__/__init__.cpython-34.pyc' rm 'superlists/__pycache__/settings.cpython-34.pyc' rm 'superlists/__pycache__/urls.cpython-34.pyc' rm 'superlists/__pycache__/wsgi.cpython-34.pyc' $ echo "__pycache__" >> .gitignore $ echo "*.pyc" >> .gitignore
Now let’s see where we are … (You’ll see I’m using
git status a lot—so
much so that I often alias it to
git st … I’m not telling you how to do
that though; I leave you to discover the secrets of Git aliases on your own!):
$ git status On branch master Initial commit Changes to be committed: (use "git rm --cached <file>..." to unstage) new file: .gitignore new file: functional_tests.py new file: manage.py new file: superlists/__init__.py new file: superlists/settings.py new file: superlists/urls.py new file: superlists/wsgi.py Changes not staged for commit: (use "git add <file>..." to update what will be committed) (use "git checkout -- <file>..." to discard changes in working directory) modified: .gitignore
Looking good, we’re ready to do our first commit!
$ git add .gitignore $ git commit
If you want to really go to town on Git, this is the time to also learn about how to push your work to a cloud-based VCS hosting service, like GitHub or BitBucket. They’ll be useful if you think you want to follow along with this book on different PCs. I leave it to you to find out how they work; they have excellent documentation. Alternatively, you can wait until Chapter 8 when we’ll be using one for deployment.
That’s it for the VCS lecture. Congratulations! You’ve written a functional test using Selenium, and you’ve gotten Django installed and running, in a certifiable, test-first, goat-approved TDD way. Give yourself a well-deserved pat on the back before moving on to Chapter 2.