The Testing Goat

Obey the Testing Goat!

TDD for the Web, with Python, Selenium, Django, JavaScript and pals...

Book upgraded to Django 1.7!

Wed 02 April 2014
By Harry

In a (futile) attempt to future-proof the book, I decided to upgrade it to Django 1.7. Here's how that went down.


Unsurprisingly, the biggest change was to do with migrations. Like any new change, my initial reaction was dislike, and I resented the new things, but I think overall it's a definite improvment.

In brief, here's what happened:

  • The new migrations framework means 'any' change to models needs a migration, or tests won't pass.

  • This meant introducing the concept of migrations much earlier in the book; in fact, at the same time as I introduce the ORM. I resented this because it made the learning curve of the book steeper.

  • On the other hand, because the new version of migrations essentially forces you to have them from the very beginning, I was able to drop an entire chapter that was devoted to retrospectively building migrations after the first deployment, which included all sorts of checking out of old versions, and using --fake, and so on. So that's a big win.

  • Using step-by-step TDD also forces you to make more migrations than you really want to, if you're adding fields and constraints step-by-step. It means I have to introduce the reader early on to the idea of replacing existing migrations too, another steepening of the learning curve.

Here's some detail on the changes.

Migrations make the introduction of more complex

In chapter 5, where we build the first model, the narrative used to go:

  1. Write a test
  2. See it fail
  3. Add code in, step by step
  4. Get the tests further, see a different failure
  5. Add more code in
  6. Get the tests passing

Now it goes:

  1. Write a test
  2. See it fail
  3. Add code in, step by step
  4. See a database error
  5. Create a migration
  6. See the tests get further, see a different failure
  7. Add more code in
  8. See a database error
  9. Explain the concept of squashing migrations into one
  10. Delete the existing migration and re-create it.
  11. See the tests pass

So you can see it's more complicated. On the other hand, understanding how Django gets from to the database is important. I had been just hand-waving and saying "use syncdb, and just delete the database if anything goes wrong", so maybe it's better to address this stuff head-on, rather than wait for a complicated later chapter.

If you're curious, you can view the whole narrative here

(If you're a Django core developer and you're reading this, I'd love to hear your thoughts btw. There's a few weeks before the book goes to print yet, so there's still time to tell me I'm doing it all wrong!)

I still kinda wish I could have kept my nice shallow learning curve - I expended a lot of effort with the book, in trying to make sure concepts are introduced one at a time and gradually, and now I feel I'm slightly forced to lump two concepts onto the reader at the same time. But, there's clearly an upside.

But they save me from a fairly horrible chapter 13...

It was always going to be an unlucky chapter wasn't it. Because I'd glossed over the concept of migrations until then, I would get the readers to deploy their code to a server in chapter 8 or so, and then code some new stuff, including a new database feature.

Then I had a chapter 13 in which we would try and deploy to the staging site, and see the new feature wouldn't work. So then I had to explain migrations, and go through this process:

  1. Find the old commit that matches the point at which we did the last deployment, and check out the old version of from it.
  2. Do a schemamigration, and create a migration to match live
  3. Check out the latest version of, and do another schemamigration to get the migration we want to apply.
  4. Test it out locally. Check out the old again, delete the databse, syncdb, then run migrate 0001 --fake, then check out the new code, and run migrate, check it works
  5. Adjust the deploy script to include migrate 0001 --fake followed by a migrate
  6. Test deploying to staging... OK
  7. Deploy to live
  8. And, don't forget to now remove the migrate 001 --fake from your deploy script.

Ouch! Quite a lot of pain there! Especially when you consider that the new procedure is:

  1. Run the deploy script. It just works, because we've had migrations all along.


Other thoughts.

I found the fact that tests would fail if you didn't have migrations intriguing, but unfortunately it's not something you can rely on. For example, in chapter 12 I introduce a unique_together constraint and test it thusly:

def test_duplicate_items_are_invalid(self):
    list_ = List.objects.create()
    Item.objects.create(list=list_, text='bla')
    with self.assertRaises(ValidationError):
        item = Item(list=list_, text='bla')

To get that passing, I just add my unique_together constraint:

class Item(models.Model):
    text = models.TextField()
    list = models.ForeignKey(List)

    class Meta:
        unique_together = ('list', 'text')

And at this point Django doesn't warn me that I need a migration, because the test is actually happening at the validation layer.

I think that's a bit of a shame, but there's probably nothing to be done about it. It's all because the concepts of data validation and database integrity constraints are separate in Django, even though their implementation in actually often happens in a single place...

One last thing...

I love the pretty colours!


comments powered by Disqus
Read the book

The book is available both for free and for money. It's all about TDD and Web programming. Read it here!

Reviews & Testimonials

"Hands down the best teaching book I've ever read""Even the first 4 chapters were worth the money""Oh my gosh! This book is outstanding""The testing goat is my new friend"Read more...


A selection of links and videos about TDD, not necessarily all mine, eg this tutorial at PyCon 2013, how to motivate coworkers to write unit tests, thoughts on Django's test tools, London-style TDD and more.

Old TDD / Django Tutorial

This is my old TDD tutorial, which follows along with the official Django tutorial, but with full TDD. It badly needs updating. Read the book instead!

Save the Testing Goat Campaign

The campaign page, preserved for history, which led to the glorious presence of the Testing Goat on the front of the book.