Search code examples
djangounit-testingtestingtdddjango-testing

How to apply Test Driven Development with Django Models?


I've recently learned about Test Driven Development and want to give it a shot while developing a new app in my Django project. I've been reading Test-Driven Development with Python which is great. However, I've sometimes found the example (To Do lists) in the book to be too simple- for instance, when testing Models is introduced, the author has a test that creates to objects, saves them, and then pulls the objects from the database to check their values. Sure, that's easy when your model only has one ModelField.

But what about when your model has twenty ModelFields? Should you have one test that creates an object, with all of its Fields, then saves that object, and then checks the value of each field? Is it better to make individual tests for each field?

In my specific case, I have a model with about five required fields, and then about fifteen more fields that are optional. My thought right now is to first have a function within my TestCase class that creates an object of this Model with the default fields. Then, I will have a test to make sure that object saves normally, and then another test for each individual optional field. Seems like a lot of tests, but isn't many small tests better than one large test?

Insight appreciated!


Solution

  • I am the author of said book. I meant that test as more of an introduction to the Django ORM, rather than as a demonstration of best practice, and I try to explain that at the time, but I suppose some confusion was inevitable. I'll have a think about how I could present things differently.

    In any case, if you skip along to a few chapters later in the book, I show how to simplify the test to something that's more best-practicey.

    Whether or not you test basic Django models is up to you -- some people will say testing a declarative syntax is over the top, others will say a short test is good to have as a placeholder. Here's one you might use:

    class Book(models.Model):
        title = models.CharField(max_length=200)
        author = models.ForeignKey(Author)
        ISBN = models.CharField(max_length=35)
        abstract = models.TextField()
    
    
    class BookTest(TestCase):
    
        def test_defaults(self):
            book = Book()
            self.assertEqual(book.title, '')
            self.assertEqual(book.author, None)
            self.assertEqual(book.ISBN, '')
            self.assertEqual(book.abstract, '')
    

    So that's a placeholder. It encourages you to add more tests if you start introducing more complex fields like, say, a publication_date field which has a default value of datetime.today() + one_month, which might warrant a bit of testing to make sure you get it right. Having a placeholder lowers the barrier to subsequent tests. Other people will tell you that's over the top. You have to find your own balance.

    One thing that's pretty widely accepted is that you should definitely test behaviour. So, if your model has a custom method:

    class Book(models.Model):
        # [...]
    
        def is_available(self):
            return self.pub_date < datetime.today() and Stock.objects.filter(book=self).count() > 0
    

    Then some sort of test for that is definitely a good idea.