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.