Testing Django Applications




Honza Král


Follow me: @honzakral
E-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 tests
from 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 tests
from 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 enough
def 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: @honzakral
E-mail me: honza.kral@gmail.com

Testing Django Applications

  • 1.
    Testing Django Applications HonzaKrál Follow me: @honzakral E-mail me: honza.kral@gmail.com
  • 2.
  • 3.
    You already do! Wheneveryou ● 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?
  • 4.
    Be lazy! Let thecomputer do the boring work.
  • 5.
    Even if youare 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?
  • 6.
    Terminology ● Unit tests ● Integration tests ● Acceptance tests ● Performance tests ● Load tests ● Stress tests ● UX tests ● .........
  • 7.
    Unit tests from unittestimport 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())
  • 8.
    Unit tests ● No interaction with other components ● Including DB ● Fast ● Tied to the code ● Testing small portion of the code
  • 9.
    Integration tests from django.testimport 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())
  • 10.
    Integration tests ● Test how components cooperate together ● Most common tests in Django apps ● Usually slower and cover more code ● Important to have
  • 11.
  • 12.
    (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)
  • 13.
    Separate components (views) defmy_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 ● ...
  • 14.
    Class-based views FTW! classMyView(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)
  • 15.
    Separate components (templatetags) 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)
  • 16.
    Testing templatetags from unittestimport 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))
  • 17.
    Testing models Try toavoid 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)
  • 18.
    Populating test DB ● fixtures only work for static data ● natural keys FTW! ● use factories when fixtures aren't enough def get_payment_assesment(stay, ** kwargs): defaults = { 'modified_by': User.objects.get_or_create(...), 'stay': stay, ...} defaults.update(kwargs) return PaymentAssesment.objects.create( **defaults)
  • 19.
    Testing forms from unittestimport 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())
  • 20.
    Tests alone arenot enough!
  • 21.
    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)
  • 22.
    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
  • 23.
    Happy hour If youhave 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!
  • 24.
  • 25.
    Thanks for listening HonzaKrál Follow me: @honzakral E-mail me: honza.kral@gmail.com