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
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',
}
}
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
- Beautiful is better than ugly.
- Explicit is better than implicit.
- Simple is better than complex.
- Complex is better than complicated.
- Flat is better than nested.
- Sparse is better than dense.
- Readability counts.
- Special cases aren't special enough to break the rules.
- Although practicality beats purity.
- Errors should never pass silently.
- Unless explicitly silenced.
- In the face of ambiguity, refuse the temptation to guess.
- There should be one-- and preferably only one --obvious way to do it.
- Although that way may not be obvious at first unless you're Dutch.
- Now is better than never.
- Although never is often better than *right* now.
- If the implementation is hard to explain, it's a bad idea.
- If the implementation is easy to explain, it may be a good idea.
- Namespaces are one honking great idea -- let's do more of those!
Sources
-
Django testing code samples docs.djangoproject.com
(especially /en/dev/topics/testing/overview/) -
Mockito API ('... a fairy dies') http://docs.mockito.googlecode.com/
-
"10% of the time, we write ugly code for performance reasons, the other 90% of the time, we write ugly code to be consistent! - @venkat_s" source
- "Data matures like wine, applications like fish" James Governor, source
- Image "Rozum i Serce" - http://www.magazynbieganie.pl/serce-i-rozum/
Follow up
Home/blog: erbetowski.pl
Twitter: erbetowski
Facebook: wojtekerbetowski
GitHub: wojtekerbetowski
Advanced testing Django applications
By Wojtek Erbetowski
Advanced testing Django applications
- 3,122