Intro al testing en python/django

Testing básico

Para asegurar que el código funciona necesitamos probarlo, por ejemplo:

1 def add(a, b):
2     """ add two numbers """
3     return a + b

Abrimos una shell python:

>>> add(1, 2)
3

Funciona, Pero tenemos siempre que escribir lo mismo cada vez que queramos ver si el código funciona...

Testing automático

creamos un fichero llamado test.py que tenga el código que pruebe esa función.

Para ello usamos el módulo unittest de python:

1 import unittest
2 
3 class TestAdd(unittest.TestCase):
4 
5     def test_add(self):
6         self.assertEqual(add(1, 2), 3)
7 
8 if __name__ == '__main__':
9     unittest.main()

Y ejecutamos el test cuando queramos para verificar que el código sigue funcionando:

1 #python test.py
2 .
3 ----------------------------------------------------------------------
4 Ran 1 test in 0.000s
5 
6 OK

Otra forma, python rocks

En python existe una cosa llamada doctest, que nos ayuda a hacer testing a la vez que documentamos. basta con poner en la documentación del método lo que pondríamos en la shell python

1 # file: add.py
2 def add(a, b):
3     """ return a added to b 
4         >>> add(1, 2)
5         4
6     """
7     return a + b

Ejecutamos en la consola:

$ python -m doctest add.py
**********************************************************************
File "add.py", line 8, in add.add
Failed example:
    add(1, 2)
Expected:
    4
Got:
    3
[...]
***Test Failed*** 1 failures.

Translademos add a django

Creemos un servicio que sume dos números (grandioso):

views.py:

1 import json
2 from django.http import HttpResponse
3 
4 def add(a, b): 
5     return a + b
6 def add_view(request, a, b):
7     return HttpResponse(json.dumps(add(int(a), int(b))), mimetype='application/json')

urls.py:

1 urlpatterns = patterns('',
2     url(r'^add/(?P<a>\d+)/(?P<b>\d+)/$', 'views.add_view', name='add')
3 )

Podemos testear con cualquier cliente http, por ejemplo:

$ curl "http://localhost:8000/add/1/2/"
3

Veamos como automatizar esto

Unit test en django

  • En cada aplicación hay un fichero llamado tests.py que hay que hacerle caso.
  • En él se encuentran los test de la aplicación y se usa django.test, un módulo muy similar a unitest (visto antes)

tests.py:

1 from django.test import TestCase
2 from add.views import add
3 
4 class AddTest(TestCase):
5     def test_add(self):
6         self.failUnlessEqual(2, add(1, 1))

ejecutamos con el comando test:

$ python manage.py test
Creating test database 'default'...
No fixtures found.
.
----------------------------------------------------------------------
Ran 1 test in 0.001s

OK
Destroying test database 'default'...

También con doctest

tests.py (ojo, al comienzo del fichero):

1 """
2 documentation here!
3 >>> from views import add
4 >>> add(1, 2)
5 3
6 """

ejecutamos con test de nuevo:

$ python manage.py test
Creating test database 'default'...
No fixtures found.
.
----------------------------------------------------------------------
Ran 1 test in 0.008s

OK

django test client

  • Si queremos hacer testing de las vistas se django trae incluído una clase Client
  • Probamos si nuestro servicio retorna lo que debe
  • django.test.TestCase tiene ya un client para usar sin necesidad de crearlo

tests.py:

 1 import json
 2 from django.test import TestCase
 3 from django.test.client import Client
 4 from django.core.urlresolvers import reverse
 5 
 6 class AddTest(TestCase):
 7 
 8     def test_add_service(self):
 9         c = Client() 
10         url = reverse('add', kwargs={'a': 1, 'b': 2}) # /add/1/2/
11         response = c.get(url)
12         self.failUnlessEqual(200, response.status_code)
13         content = json.loads(response.content)
14         self.failUnlessEqual(3, content)

ejecutamos, igual que siempre, con el comando test

django test client, doctest

  • También podemos ejecutar el test con doctest

tests.py:

 1 """
 2 >>> from django.test.client import Client
 3 >>> from django.core.urlresolvers import reverse
 4 >>> c = Client()
 5 >>> url = reverse('add', kwargs={'a': 1, 'b': 2}) # /add/1/2/
 6 >>> url
 7 '/add/1/2/'
 8 >>> response = c.get(url)
 9 >>> response.status_code
10 200
11 >>> json.loads(response.content)
12 3
13 """

Links