Chapter 1. Getting Django Set Up Using a Functional Test
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.
Obey the Testing Goat! Do Nothing Until You Have a Test
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.
Weâll proceed with nice small steps; weâre going to use Django, which is a popular Python web framework, to build our app.
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:
functional_tests.py
from
selenium
import
webdriver
browser
=
webdriver
.
Firefox
()
browser
.
get
(
'http://localhost:8000'
)
assert
'Django'
in
browser
.
title
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, in a bit. For now, itâs enough to assure ourselves that we understand what itâs doing:
-
Starting a Selenium âwebdriverâ to pop up a real Firefox browser window
-
Using it to open up a web page which weâre expecting to be served from the local PC
-
Checking (making a test assertion) that the page has the word âDjangoâ in its title
Letâs try running it:
$ python functional_tests.py File ".../selenium/webdriver/remote/webdriver.py", line 324, in get self.execute(Command.GET, {'url': url}) File ".../selenium/webdriver/remote/webdriver.py", line 312, in execute self.error_handler.check_response(response) File ".../selenium/webdriver/remote/errorhandler.py", line 242, in check_response raise exception_class(message, screen, stacktrace) selenium.common.exceptions.WebDriverException: Message: Reached error page: abo ut:neterror?e=connectionFailure&u=http%3A//localhost%3A8000/[...]
You should see a browser window pop up and try to open localhost:8000, and show the âUnable to connectâ error page. If you switch back to your console, youâll see the big ugly error message, telling us that Selenium hit an error page. And then, you will probably be irritated at the fact that it left the Firefox window lying around your desktop for you to tidy up. Weâll fix that later!
Note
If, instead, you see an error trying to import Selenium, or an error trying to find âgeckodriverâ, you might need to go back and have another look at the "Prerequisites and Assumptions" section.
For now though, we have a failing test, so that means weâre allowed to start building our app.
Getting Django Up and Running
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 .
Donât forget that â.â at the end; itâs important!
That will create a file called manage.py in your current folder, and a subfolder called superlists, with more stuff inside it:
âââ functional_tests.py âââ geckodriver.log âââ manage.py âââ superlists â âââ __init__.py â âââ settings.py â âââ urls.py â âââ wsgi.py âââ virtualenv âââ [...]
Note
Make sure your project folder looks exactly like this! If you see two nested folders called superlists, itâs because you forgot the â.â above. Delete them and try again.
The superlists folder is intended for stuff that applies to the whole projectâlike settings.py, for example, which is used to store global configuration information for the site.
But the main thing to notice is 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:
$ python manage.py runserver Performing system checks... System check identified no issues (0 silenced). You have 13 unapplied migration(s). Your project may not work properly until you apply the migrations for app(s): admin, auth, contenttypes, sessions. Run 'python manage.py migrate' to apply them. Django version 1.11.3, using settings 'superlists.settings' Starting development server at http://127.0.0.1:8000/ Quit the server with CONTROL-C.
Note
Itâs safe to ignore that message about âunapplied migrationsâ for now. Weâll look at migrations in Chapter 5.
Thatâs Djangoâs development server now up and running on our machine.
Leave it there and open another command shell. Navigate to your project folder, activate your virtualenv, and then try running our test again:
$ python functional_tests.py $
Tip
If you see an error saying âno module named seleniumâ, youâve forgotten to activate your virtualenv. Check the Prerequisites and Assumptions section again if you need to.
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.
Starting a Git Repository
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.
Letâs start by doing the git init
to start the repository:
$ ls db.sqlite3 functional_tests.py geckodriver.log manage.py superlists virtualenv $ git init . Initialised empty Git repository in ...python-tdd-book/.git/
Now letâs take a look and see what files we want to commit:
$ ls db.sqlite3 functional_tests.py geckodriver.log manage.py superlists virtualenv
There are a few things in here that we donât want under version control: db.sqlite3 is the database file, geckodriver.log contains Selenium debug output, and finally our virtualenv shouldnât be in git either. Weâll add all of them to a special file called .gitignore which, um, tells Git what to ignore:
$ echo "db.sqlite3" >> .gitignore $ echo "geckodriver.log" >> .gitignore $ echo "virtualenv" >> .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-36.pyc new file: superlists/__pycache__/settings.cpython-36.pyc new file: superlists/__pycache__/urls.cpython-36.pyc new file: superlists/__pycache__/wsgi.cpython-36.pyc new file: superlists/settings.py new file: superlists/urls.py new file: superlists/wsgi.py
Oops! 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-36.pyc' rm 'superlists/__pycache__/settings.cpython-36.pyc' rm 'superlists/__pycache__/urls.cpython-36.pyc' rm 'superlists/__pycache__/wsgi.cpython-36.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
When you type git commit
, it will pop up an editor window for you to write
your commit message in. Mine looked like
Figure 1-3.1
Note
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 9 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.
1 Did vi pop up and you had no idea what to do? Or did you see a message about account identity and git config --global
user.username
? Go and take another look at âPrerequisites and Assumptionsâ; there are some brief instructions.
Get Test-Driven Development with Python, 2nd Edition now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.