Tests en Django: Como escribir tests de manera más sencilla
In the last pybirras-tenerife I gave a brief talk about testing Django apps, and I promised to publish a post in spanish with more in-depth info about that. So if you can’t read spanish I apologize for the inconvenience.
Quizás quieras consultar el primer artículo de la serie.
El problema
Los tests son realmente difíciles de escribir si se escriben después del código, ya que una vez escrita una funcionalidad todos tenemos la tendencia a seguir escribiendo funcionalidades y no perder el tiempo probando las que ya tenemos. Para combatir, y muchas otras cosas, esto surgen metodologías como TDD (Test Driven Development) que proponen escribir las pruebas antes que el código. Si quieres saberlo todo sobre TDD te recomiendo el gran libro del gran Carlos Ble, también te recomiendo que obedezcas a la cabra.
Con todo TDD tiene un problema, los tests tienen que ser escritos por técnicos. Surge pues BDD (Behavior Driven Development). La idea aquí es que los tests sean muy cercanos al lenguaje natural, por tanto podrán ser escritos por los dueños de producto, y luego los técnicos convertirán esto en un test automatizado. Veamos como se especifica una tests.
Tests en Django con behave
Feature: Historia de usuarios 1
Como usuario registrado
Yo quiero poder listar mis posts
Para saber que publiqué
@trivial
Scenario: Usuario sin posts publicados
Given Soy un usuario sin posts publicados
When Visito la página de listar posts
Then Dice que "No hay posts"
Como se puede ver el idioma del tests, es muy similar al lenguaje natural, y lo sería aún más si usáramos inglés.
Este ejemplo es un test de behave válido si implementamos los pasos necesarios, utiliza una aproximación a las historias de usuarios, donde en la primera parte especificamos la historia y en la segunda desarrollamos los criterios de aceptación. Existen otras herramientas como RSpec que utilizan especificaciones en lugar de historias de usuarios.
La manera de escribir los pasos no podría ser más sencilla.
from behave import *
from mock import Mock
from usuario.models import Usuario
@given("Soy un usuario sin posts publicados")
def step_impl(context):
context.user = Usuario.objects.create(username="Bar", password="Foo")
context.browser = webdriver.Chrome()
@when("Visito la página de listar posts")
def step_impl(context):
context.browser.get('http://localhost:8000/posts/list')
@then('Dice que "{text}"')
def step_impl(context, text):
assert context.browser.text_is_present(text)
Este fichero debe encontrarse dentro del paquete “steps” de la carpeta features.
Como podemos ver en el último paso, es posible suministrar parámetros a los pasos, lo cual hace que podamos crear una librería de pasos para nuestros tests en django.
Para poder ejecutar los tests, necesitamos configurar el test runner, y ejecutar los tests como siempre.
TEST_RUNNER = 'django_behave.runner.DjangoBehaveTestSuiteRunner'
Una cosa interesante que diferencia a behave de lettuce es la posibilidad de marcar a cada escenario. En nuestro escenario de ejemplo el @trivial es un tag, podemos poner el que queramos. Para ejecutar solo los tests que tengan una marca podemos hacer
$ python manage.py test app1 --behave_tags @trivial
Hay veces que es conveniente ejecutar algo antes o después de un paso, escenario, feature, tag o todo
Esto se puede hacer en el fichero environment.py que se debe encontrar en el directorio de las features. En este fichero podremos implementar los métodos before_step, before_scenerario, before_feature, before_tag y before_all (y lo mismo con after_). Ejemplos de cosas que se pueden hacer aquí incluyen levantar el webdriver de selenium, cargar con factory boy los datos que necesitemos para nuestros tests, etc.
Además una cosa importante es que podemos utilizar coverage junto con todo esto.
$ coverage run --source "." --omit "*/features/*,*/migrations/*" manage.py test app1 app2
$ coverage report -m
Name Stmts Miss Cover Missing
-------------------------------------------------------
my_program 20 4 80% 33-35, 39
my_other_module 56 6 89% 17-23
-------------------------------------------------------
TOTAL 76 10 87%
En la próxima entrada de esta serie (que espero que sea la última), integraremos nuestro proyecto con jenkins para poder pasar los tests, sacar estadísticas de cobertura de código y examinar los ficheros python en busca de violaciones de pep8 y pylint.