Como hacer más robustas nuestras apps con UnitTest en Django
Después de unas más que necesarias vacaciones vuelvo a la carga con nuevas ideas y más energía para traeros nuevo material 💪. Para el tutorial de hoy vamos a aprender a integrar test unitarios en nuestras aplicaciones desarrolladas con Django.
Pero antes de nada para el que no lo tenga claro...
¿Qué son los tests unitarios o unittests?
Los tests unitarios son scripts que generamos para verificar el correcto funcionamiento de un fragmento de nuestro código. Esto nos servirá para corregir posibles errores cuando realicemos modificaciones en nuestros proyectos que si no los implementásemos puede que se nos escapasen. Si queréis más información acerca de los test unitarios en Python os aconsejo que veáis mi tutorial sobre como implementarlos en un CRUD con Python antes de empezar con este tutorial.
¿Cómo podemos usarlo en Django?
En una app creada en Django podríamos usarlo por ejemplo para verificar que una página funciona correctamente, testar redirecciones, crear usuarios, crear datos en la base de datos y muchas cosas más.
Lo bueno de los tests en Django es que para realizar las pruebas, Django crea una BBDD nueva con la que realizar los tests, de esta forma no se comprometen los datos que tenemos en la base de datos original, así que muy importante es que el usuario que utilizamos para conectarnos a la base de datos de nuestro proyecto sea un usuario con permisos para crear una base de datos, si no fallará el proceso. Otra cosa importante es que al finalizar el proceso se eliminará la base de datos al menos que le indiques lo contrario.
Explicado esto vamos al lío 🙃. Para este ejemplo vamos a utilizar el proyecto que ya hemos utilizado en otras ocasiones que es el blog que creamos hace algún tiempo, si quieres saber más sobre este proyecto te aconsejo que mires este link donde explico como crear un blog con Django.
Implementar nuestros tests en Django
Cada vez que creamos una app con el manage.py (python manage.py startapp <nombre-app>) se genera un archivo llamado tests.py que es donde guardaremos nuestros tests. Para este primer ejemplo vamos a testar que las páginas de login y registro estén activas y además vamos a realizar el login y un registro para verificar que funcionan correctamente estas funcionalidades.
Para ello abrimos el archivo users/tests.py y añadimos las siguientes librerías que son las que usaremos:
users/tests.py
# Django
from django.test import TestCase
from django.test import Client
# Python
from http import HTTPStatus
# Models
from django.contrib.auth.models import User
La primera es TestCase que es la clase de la que extenderá nuestra clase en este test y que se utiliza para heredar los métodos de tests de unittests.TestCase.
La siguiente es Client. Esta clase la utilizaremos para realizar peticiones a nuestra web de forma sencilla en la que le podremos pasar el método (GET, POST, etc.) y opcionalmente parámetros y configuraciones.
De Python utilizaremos la clase HTTPStatus que contiene las constantes con los estados de una petición a un servicio web (200 para ok, 404 para not found, etc.).
Por último traemos el modelo de usuarios, ya que lo necesitaremos para la creación de usuarios y verificación de su existencia.
Lo siguiente que haremos será definir la clase y verificar que están operativas tanto la página de login como la de registro, para ello añadimos el siguiente código:
class UserTestCase(TestCase):
def setUp(self):
self.c = Client()
def test_is_ok_page_login(self):
response = self.c.get('/login')
self.assertEqual(response.status_code, HTTPStatus.OK)
def test_is_ok_page_register(self):
response = self.c.get('/registro')
self.assertEqual(response.status_code, HTTPStatus.OK)
Lo primero que hacemos es definir la clase que extenderá de TestCase. Para que funcione correctamente el nombre de la clase debe terminar con TestCase y el formato del nombre debe ser CamelCase.
Lo siguiente que hacemos es crear la función setUp. Esta función se ejecutará justo antes del lanzamiento de un test. En este caso queremos que para cada test nos cree una instancia de la clase Client, ya que la vamos a utilizar en todos nuestros tests.
Bien, y ahora llega el primer test llamado test_is_ok_page_login que lanzará una petición get a la página de login y verificará con assertEqual (que es uno de los métodos de unittest) si devuelve el estado que queremos (en este caso debe devolver el estado 200 que significa que todo ha ido bien). Importante, todos los test que creemos deben empezar por la palabra test_ y tener formato snake_case, si no no los ejecutará. Para verificar la página de registro haremos exactamente lo mismo.
Ahora que ya tenemos los tests para comprobar el estado de las páginas de login y registro vamos a verificar que el login funciona correctamente, para ello añadimos el siguiente test:
def test_login_user(self):
credentials = {
'username': 'Alber Login',
'password': '4ABXX4S9CuB3rgVM'
}
user = User.objects.create_user(**credentials)
response = self.c.post('/login', credentials, follow=True)
self.assertTrue(response.context['user'].is_active)
Para este test definimos unas credenciales de usuario que utilizaremos para crear un usuario con User.objects.create_user. Esta función creará un usuario con el estado is_active a True por defecto.
Después lanzamos una petición a la página login por post enviando los credenciales y añadiendo el parámetro follow=True. Esto permitirá que cuando se lance la petición y se realicen redirecciones, Client las siga hasta el final, si no añadimos este parámetro no se realizará el login correctamente.
Por último verificamos el test con asserTrue que verifica que el parámetro pasado es igual a True.
Una vez creado el test para el login, es hora de comprobar que podemos registrar un usuario, para ello vamos a añadir el siguiente código:
def test_register_user(self):
data = {
'username': 'TestAlber',
'email': 'alber@cosasdedevs.com',
'password': '4ABXX4S9CuB3rgVM',
'password_confirmation': '4ABXX4S9CuB3rgVM',
}
response = self.c.post('/registro', data)
try:
user = User.objects.get(username=data['username'])
except User.DoesNotExist:
user = NULL
self.assertIsInstance(user, User)
Para este test creamos un diccionario con los parámetros que tenemos para crear un usuario y después lo lanzamos a la página de registro por el método post para crear el usuario.
Después verificamos si el usuario existe utilizando User.objects.get y enviando el nombre de usuario que hemos definido anteriormente. Este tipo de consultas solo debemos usarlos con campos únicos, ya que si usamos un campo que no es único podemos obtener otro usuario y validar el test erróneamente si hay algún error.
Por último verificamos la validez del test con assertIsInstancce que verifica si el resultado de nuestra query es una instancia de la clase User. Si es así lo dará como válido.
Además de estos test también podríamos hacer tests con datos erróneos para comprobar que recibimos el error que esperamos. ¡Las posibilidades son infinitas!
Para lanzar nuestros tests solo tenemos que ejecutar el siguiente comando:
python manage.py test
Este comando lanzará todos los test que tengamos en nuestro proyecto. Si tuviéramos varios tests y solo quisiéramos ejecutar los de la app users lo haríamos de la siguiente forma:
manage.py test users.tests
Si queréis saber más de los tests en Django os dejo la documentación para que le echéis un vistazo 😁.
Conclusiones
Los tests con Django son fáciles de implementar y nos pueden ahorrar más de un dolor de cabeza así que os recomiendo que los implementéis en vuestros proyectos.
También os dejo el enlace al proyecto en mi GitHub por si queréis echarle un vistazo.
Espero que este post te ayude y como siempre, te recomiendo seguirme en Twitter para estar al tanto de los nuevo contenido. Ahora también puedes seguirme en Instagram donde estoy subiendo tips, tutoriales en vídeo e información sobre herramientas para developers.
Por último os dejo mi guía para aprender a trabajar con APIs donde explico todo el funcionamiento de una API, el protocolo HTTP y veremos como construir una API con arquitectura REST.
Nos leemos 👋.