Test Driven Development
With Python and Django
Israel Saeta Pérez
¿Por qué hacer tests?
- Dormir tranquilo
- Rápida incorporación gente nueva
- Desarrollo más rápido. En serio.
¿Qué es TDD?
- Escribir test que falla
- Escribir código mínimo para que pase
- Refactorizar código para dejarlo más bonito
Teeest, teeest first!
(source: Caitlin Stewart, on Flickr)
unittest
# test*.py
import unittest
from mymodule import Rounder
class TestRounder(unittest.TestCase):
# called before each test method (setup)
# can be used to create fixtures
def setUp(self):
self.rounder = Rounder()
def tearDown(self): # called after each test method (cleanup)
self.rounder.close()
def test_round_down(self): # all tests method names start with 'test_'
self.assertEqual(self.rounder.round(3.1), 3)
if __name__ == '__main__': # or 'python -m unittest discover'
unittest.main()
unittest
dukebody@pepino:~/develop/testing > python test.py
F
======================================================================
FAIL: test_round_down (__main__.TestRounder)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 14, in test_round_down
self.assertEqual(self.rounder.round(3.1), 3)
AssertionError: None != 3
----------------------------------------------------------------------
Ran 1 test in 0.001s
FAILED (failures=1)
unittest
dukebody@pepino:~/develop/testing > python test.py
.
----------------------------------------------------------------------
Ran 1 test in 0.001s
Django
# rollback transaction on teardown
# isolated test db
from django.test import TestCase # transaction for each test method
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"')
django-admin test [module.path]
Testing views
from django.test import TestCase
# use built-in test client
class MyTestCase(TestCase):
def test_create_book(self):
"""
Posting creates a new book.
"""
data = {'title': 'The catcher in the rye'}
response = self.client.post('/books/', data) # urlresolver
books = Book.objects.all()
self.assertEquals(len(books), 1)
self.assertEqual(books[0].title, data['title']
Testing settings
from django.test import TestCase, override_settings
class LoginTestCase(TestCase):
@override_settings(LOGIN_URL='/other/login/')
def test_login(self):
response = self.client.get('/sekrit/')
self.assertRedirects(response, '/other/login/?next=/sekrit/')
Functional tests
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
import unittest
class NewVisitorTest(unittest.TestCase):
def setUp(self):
self.browser = webdriver.Firefox()
self.browser.implicitly_wait(3)
def tearDown(self):
self.browser.quit()
def test_can_start_a_list_and_retrieve_it_later(self):
# Edith has heard about a cool new online to-do app. She goes
# to check out its homepage
self.browser.get('http://localhost:8000')
# She notices the page title and header mention to-do lists
self.assertIn('To-Do', self.browser.title)
header_text = self.browser.find_element_by_tag_name('h1').text
self.assertIn('To-Do', header_text)
Tips, tricks and goodies
For everything else, use Google
Identical DB types for testing and prod
From working code to working code
Obey the Testing Goat, not the Refactoring Cat!
factory_boy
# tests.py
class FooTests(unittest.TestCase):
def test_with_factory_boy(self):
# We need a 200€, paid order,
# shipping to australia, for a VIP customer
order = OrderFactory(
amount=200,
status='PAID',
customer__is_vip=True,
address__country='AU',
)
# Run the tests here
# factories.py
import factory
from . import models
class UserFactory(factory.Factory):
class Meta:
model = models.User
first_name = 'John'
last_name = 'Doe'
admin = False
nose and django-nose
- DB reuse - go faster
- pdb on failure
- plugin extensions
Mock
- Mock calls to external services (ex. APIs)
- Make unit tests really unit
- Assert that a method was called
from freezegun import freeze_time
@freeze_time("2012-01-14")
def test():
assert datetime.datetime.now() == datetime.datetime(2012, 01, 14)
If your code calls datetime.now
Tox
dukebody@pepino:~/develop/sklearn-pandas > cat tox.ini
[tox]
envlist = py27, py34
[testenv]
deps =
pip==7.0.1
pytest==2.7.1
setuptools==16.0
wheel==0.24.0
flake8==2.4.1
py27: mock==1.3.0
commands =
flake8 tests
py.test
Best reference
Test Driven Development
By Israel Saeta Pérez
Test Driven Development
Intro to Test Driven Development with Python and Django. Some cool tricks are mentioned towards the end.
- 2,905