Testing


Complex
Django
Applications

Wojtek Erbetowski

Programmer
Groovy, Scala, Python, Java

Organizer
Warszawa Java User Group, Warsjawa, GitKata, NameCollision, MobileWarsaw, Mobile Central Europe

Tech Lead @ Polidea

Speaker

Creator of RoboSpock

The context


Deep JVM experience

Strong Groovy/Scala influence

Idiomatic everything!

TDD enthusiast


What do the Docs say?

from django.test import TestCase
from myapp.models import Animal

class AnimalTestCase(TestCase):
    def setUp(self):
        Animal.objects.create(name="lion", sound="roar")
        Animal.objects.create(name="cat", sound="meow")

    def test_animals_can_speak(self):
        """Animals that can speak are correctly identified"""
        lion = Animal.objects.get(name="lion")
        cat = Animal.objects.get(name="cat")
        self.assertEqual(lion.speak(), 'The lion says "roar"')
        self.assertEqual(cat.speak(), 'The cat says "meow"')

class SimpleTest(TestCase):
    def test_details(self):
        response = self.client.get('/customer/details/')
        self.assertEqual(response.status_code, 200)

Using both model and client


from django.test import TestCase

class SimpleTest(TestCase):
  def test_details(self):    self.assertFalse(User.objects.filter(name = 'John').exists())
    response = self.client.post('/users', {"name": "John"})
    self.assertEqual(response.status_code, 200)
    self.assertTrue(User.objects.filter(name = 'John').exists())


Application layers vs tests


Testing through API only


from django.test import TestCase

class SimpleTest(TestCase):
  def test_details(self):    user_list = self.client.get('/users?name=John'}).content    # assert empty list
    response = self.client.post('/users', {"name": "John"})
        user_list = self.client.get('/users?name=John').content    # assert NON empty list

Application layers vs tests


Why unit testing?

Using unittest.TestCase avoids the cost of running each test in a transaction and flushing the database, but if your tests interact with the database their behavior will vary based on the order that the test runner executes them. This can lead to unit tests that pass when run in isolation but fail when run in a suite.


Why unit testing?


  • fast feedback loop
  • isolation
  • Red-Green-Refactor
  • architecture

Dependency Injection

Mocking to the rescue


@patch('models.User.objects.create')def user_should_be_created_with_email():    User.register(name = "John", email="j@me.com")
self.assertEquals( User.objects.create.calls[2]['email'],  ("j@me.com",) )

RETURNS_DEEP_STUBS




Good quote I've seen one day on the web: 
every time a mock returns a mock a fairy dies.

Mocking the Django way

Running the tests

All tests at once


  ./manage.py test

Separating Unit/Integration Tests


 ./manage.py test --pattern="tests_unit_*.py"

Unit tests runner


  • fast enough
  • reliable
  • independent
  • parallel (!)

Fast enough!


Treat your tests well


They are as important as production code

Tests looking similar?


from django.test import TestCase

class SimpleTest(TestCase):
    def test_details(self):        response = self.client.get('/customer/details/')
self.assertEqual(response.status_code, 200)
# extract details response = self.client.post('/customer/connect', data=details) self.assertEqual(response.status_code, 200)
# validate response

SuperClient!


class SimpleTest(MyOwnSuperHeroTestCase):
    def test_details(self):        details = self.client.get_customer_details()
result = self.client.connect_customer(details)
# Verify output

Knowing too much?


children = Person.objects.filter(age__lte=18).all()

for child in children:
    child.age = 18
    child.save()

self.assertEquals(len(Persons.adults.all()), 1)

Tests love encapsulation


children = Person.children.all()

for child in children:
    child.age = 18
    child.save()

self.assertEquals(len(Persons.adults.all()), 1)

Convention conflicts


class TwitterConnectorTest(unittest.TestCase):    def setUp():        self.twitter_connector = ...
def should_load_ten_tweets():
tweets = self.twitter_connector.load_tweets() self.assertTrue(len(tweets) == 10)

Nose tests


 class TwitterConnectorTest(unittest.TestCase):
    def setup():
        self.twitter_connector = ...

    def should_load_ten_tweets():
        tweets = self.twitter_connector.load_tweets()
        assert len(tweets) == 10

Generators, XUnit output, timeouts

Integration testing environment



Mocking everything out?


DATABASES = {    'default': {        'ENGINE': 'django.db.backends.sqlite3'    }}
 CACHES = {
    'default': {
        'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
    }
}

E-mail outbox, 3rd party services and more

Meet the dependencies


When do you first meet your real dependencies?

UAT?
Preproduction?
Production?

Using real dependencies


build steps: vagrant up

Continuous Integration & Vagrant



Zen

  1. Beautiful is better than ugly.
  2. Explicit is better than implicit.
  3. Simple is better than complex.
  4. Complex is better than complicated.
  5. Flat is better than nested.
  6. Sparse is better than dense.
  7. Readability counts.
  8. Special cases aren't special enough to break the rules.
  9. Although practicality beats purity.
  10. Errors should never pass silently.
  11. Unless explicitly silenced.
  12. In the face of ambiguity, refuse the temptation to guess.
  13. There should be one-- and preferably only one --obvious way to do it.
  14. Although that way may not be obvious at first unless you're Dutch.
  15. Now is better than never.
  16. Although never is often better than *right* now.
  17. If the implementation is hard to explain, it's a bad idea.
  18. If the implementation is easy to explain, it may be a good idea.
  19. Namespaces are one honking great idea -- let's do more of those!

Sources


Follow up


Home/blog: erbetowski.pl
Twitter: erbetowski
Facebook: wojtekerbetowski

Advanced testing Django applications

By Wojtek Erbetowski

Advanced testing Django applications

  • 2,867