Joseph
Joseph

Reputation: 13198

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!

Upvotes: 3

Views: 920

Answers (3)

hwjp
hwjp

Reputation: 16091

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.

Upvotes: 4

Daniel Roseman
Daniel Roseman

Reputation: 599956

Why would you test the saving and loading of values to the database? That's Django's responsibility. Django has a whole suite of tests that check in detail that its database layer is working. There is absolutely no reason to check the basic behaviour of any of your fields, let alone all of them.

Your unit tests are for your logic: your custom methods, your views, your template tags. They are not for logic that is provided by Django by default.

Upvotes: 1

alecxe
alecxe

Reputation: 474161

This is where model factories would help a lot. There are two popular modules that provide it:

I've personally used factory_boy and find it very easy to use.

Basically, you define a factory with default field values:

class UserFactory(factory.Factory):
    class Meta:
        model = models.User

    first_name = 'John'
    last_name = 'Doe'
    admin = False

Then, you can use the factory and override field values if needed. It also supports Sequences, Lazy Attributes and other useful features for generating the data.

Speaking about your particular task, try not to test what django actually has tests for. For example, there is no need to test whether required argument works. Test your custom model logic involved in manipulating the model.

Also, A Guide to Testing in Django is a good read.

Hope that helps.

Upvotes: 1

Related Questions