Testing Django Applications

Post on 14-Jan-2015

2,166 views 0 download

Tags:

description

Talk given at euro djangocon 2010

transcript

Testing Django Applications

Honza Král

Follow me: @honzakralE-mail me: honza.kral@gmail.com

Why Test?

You already do!

Whenever you● open your browser to look at the result● run some part of your code in a console● look into the database directly

Is it fun?

Be lazy!

Let the computer do the boring work.

Even if you are perfect programmers (which you are not)

Tests will make you fearless of:● deployment (will this work?)● refactoring (what will this break?)● new developers (can I trust them?)● regressions (didn't I fix this before?)

Why assume it's OK when you can test?

Terminology

● Unit tests● Integration tests● Acceptance tests● Performance tests● Load tests● Stress tests● UX tests● .........

Unit testsfrom unittest import TestCase

class TestInterview(TestCase):def setUp(self):

self.interview = Interview( ... )

def test_ask_indicators_when_active(self): self.assertEquals(

True, self.interview.asking_started()) self.assertEquals(

False, self.interview.asking_ended())self.assertEquals(

True, self.interview.can_ask())

Unit tests

● No interaction with other components● Including DB

● Fast● Tied to the code● Testing small portion of the code

Integration testsfrom django.test import TestCase

class TestInterview(TestCase):def setUp(self):

self.interview = Interview.objects.create(...)

def test_unanswered_questions(self):q = Question.objects.create(

interview=self.interview,content='What ?')

self.assertEquals([q],self.interview.unanswered_questions())

Integration tests

● Test how components cooperate together● Most common tests in Django apps● Usually slower and cover more code● Important to have

Testability

(unit) testability

● avoid side effects● separate components● only accept parameters you need

● request is bad

● be smart about transactions and models● don't put busines logic into views (use models)

Separate components (views)def my_view(request, some_id, action, ...):

main_model = get_object_or_404(M, pk=some_id)other = main_model.method(action)... compute ... some ... data... return render_to_response(...)

● To test this you need to:● define the template● mock request or use test client● access the database● ...

Class-based views FTW!class MyView(object):

def get_objects(self, some_id, action): ...

def render_response(self, context): ...

def compute_data(self, m1, m2): ...

def __call__(self, request, some_id, action, ...):m1, m2 = self.get_objects(some_id, action)context = self.compute_data(m1, m2)return self.render_response(context)

● now you can easily test individual methods● or just the interesting ones (compute_data)

Separate components (template tags)

def test_parse_fails_on_too_few_arguments(self):self.assertRaises(

TemplateSyntaxError, _parse_box,['box', 'box_type', 'for'])

def test_parse_box_with_pk(self):node = _parse_box(['box', 'box_type', 'for',

'core.category', 'with', 'pk', '1'])

self.assertTrue(isinstance(node, BoxNode))self.assertEquals('box_type', node.box_type)self.assertEquals(Category, node.model)self.assertEquals(('pk', '1'), node.lookup)

Testing templatetags

from unittest import TestCase

class TestRenderTag(TestCase):def setUp(self):

self.template = template.Template('{% load myapp %}{% render var %}')

def test_does_not_escape_output(self):c = template.Context({'var': '<html> ""'})self.assertEquals(

'<html> ""', self.template.render(c))

Testing models

Try to avoid DB

def test_taller_img_gets_cropped_to_ratio(self):format = Format(

max_height=100, max_width=100)i = Image.new('RGB', (100, 200), "black")f = Formatter(i, format)

i, crop_box = f.format()self.assertEquals(

(0, 50, 100, 150), crop_box)self.assertEquals((100, 100), i.size)

Populating test DB

● fixtures only work for static data● natural keys FTW!● use factories when fixtures aren't enoughdef get_payment_assesment(stay, ** kwargs):

defaults = {'modified_by':

User.objects.get_or_create(...),'stay': stay,...}

defaults.update(kwargs)return PaymentAssesment.objects.create(

**defaults)

Testing forms

from unittest import TestCase

class TestPaymentAssesmentFormSet(TestCase):def setUp(self):

self.data = {…}

def test_formset_validates_valid_data(self):fset = PaymentAssesmentFormSet(self.data)self.assertTrue(fset.is_valid())

def test_fail_for_change_inside_a_month(self):self.data['form-0-valid_to'] = '1.06.2009'self.data['form-1-valid_from'] = '2.06.2009'fset = PaymentAssesmentFormSet(self.data)self.assertFalse(fset.is_valid())

Tests alone are not enough!

Infrastructure

● simple and convenient way to run tests● fast tests (or way to run just part of the suite)● never let your test suite break● continuous integration

● reporting● leaving this to the experts

(assuming they catch their flight)

Other requirements

● when test fails you must know what went wrong● no doctests● descriptive test names● short tests touching minimal amount of code

● write even trivial tests as a starting point● make sure tests work

Happy hour

If you have done your tests right, you will get extras:● convenient way to bootstrap your application● no need for server/browser during development● calmer nerves● people will trust your work!

?

Thanks for listening

Honza Král

Follow me: @honzakralE-mail me: honza.kral@gmail.com