Una de las secciones principales cuando desarrollamos un software es la realización de las llamadas Unit Test o Pruebas Unitarias, estas nos ayudan a probar las diferentes funcionalidades que hemos programado en el software, por ello hoy veremos como poder realizarlas cuando se realiza un proyecto utilizando Python y si además, utilizas Django, también te mostraré algunos ejemplos.

PYTEST

Un módulo o librería para poder realizar nuestras pruebas es Pytest, es muy conocida entre desarrolladores Python debido a su facilidad y utilidad.

Para instalarla basta con escribir el comando

pip install pytest

Luego de ello se recomiendo crear un archivo llamado pytest.ini para poder indicar las diferentes formas en que queremos que Pytest reconozca y busque los archivos dentro de nuestro proyecto donde se encontrarán nuestras pruebas.

pytest.png

Como se puede ver, definimos la línea python_files = tests.py test_*.py *_test.py esto indica que los archivos que debe buscar Pytest al ejecutarse son todos aquellos que tengan esa estructura de nombre, teniendo en cuenta esto es que procederemos a crear nuestro primer archivo de pruebas.

Crearemos una carpeta llamada tests, dentro de ella un archivo llamado test_user donde escribiremos nuestras pruebas para verificar la creación de Usuarios en nuestro sistema.

Para crear nuestra primera prueba debemos importar pytest y nuestro modelo a utilizar, el modelo a utilizar es el siguiente:

class Usuario(AbstractBaseUser, PermissionsMixin):
    username = models.CharField('Nombre de usuario',unique = True, max_length=100)
    email = models.EmailField('Correo Electrónico', max_length=254,unique = True)
    nombres = models.CharField('Nombres', max_length=200, blank = True, null = True)
    apellidos = models.CharField('Apellidos', max_length=200,blank = True, null = True)
    rol = models.ForeignKey(Rol, on_delete=models.CASCADE,blank = True,null = True)
    imagen = models.ImageField('Imagen de Perfil', upload_to='perfil/', max_length=200,blank = True,null = True)
    is_active = models.BooleanField(default = True)
    is_staff = models.BooleanField(default = False)
    objects = UsuarioManager()

    USERNAME_FIELD = 'username'
    REQUIRED_FIELDS = ['email','nombres']

    class Meta:
        permissions = [('permiso_desde_codigo','Este es un permiso creado desde código'),
                        ('segundo_permiso_codigo','Segundo permiso creado desde codigo')]

    def __str__(self):
        return f'{self.nombres},{self.apellidos}'

Ahora debemos definir una función donde colocaremos el código de nuestra prueba, cada función será una prueba, hay que saber que cada prueba debe evaluar un sólo tema en específico, es lo recomendado, por lo tanto vamos a crear una prueba para evaluar la Creación de un Usuario Normal.

import pytest
import apps.usuario.models import Usuario

def test_common_user_creation():
    user = Usuario.objects.create_user(
            username='dasdas',
            email='asdasda@gmail.com',
            nombres='wegw gwgwe',
            password='12345',
            is_staff=False
    )
    assert user.username == 'dasdas'

Esta prueba ejecuta la creación de un Usuario con atributos de prueba, recordemos que se crea una Base de Datos de pruebas para realizarlas por lo que no se afectará la Base de Datos que se utiliza en desarrollo, sin embargo, Pytest necesitará acceso a la Base de Datos, por lo que tal como está nuestro código dará como resultado un error, para darle acceso a la Base de Datos debemos utilizar el decorador llamado @pytest.mark.django_db quedando nuestro código de la siguiente manera:

import pytest
import apps.usuario.models import Usuario

@pytest.mark.django_db
def test_common_user_creation():
    user = Usuario.objects.create_user(
            username='dasdas',
            email='asdasda@gmail.com',
            nombres='wegw gwgwe',
            password='12345',
            is_staff=False
    )
    assert user.username == 'dasdas'

Algo que debemos tener en cuenta es que la palabra reservada assert verifica si la condición es correcta, la prueba pasará, sino generará un error por lo que tenemos que tener cuidado al momento de poner la condición.

De esta forma podemos crear mas pruebas parecidas que evalúen mas tipos de creaciones de usuarios, como usuarios superusuarios y usuarios staff.

import pytest

from apps.usuario.models import Usuario

@pytest.mark.django_db
def test_common_user_creation():
    user = Usuario.objects.create_user(
            username='dasdas',
            email='asdasda@gmail.com',
            nombres='wegw gwgwe',
            password='12345',
            is_staff=False
    )
    assert user.username == 'dasdas'

@pytest.mark.django_db
def test_superuser_creation():
    user = Usuario.objects.create_superuser(
            username='dasdas',
            email='asdasda@gmail.com',
            nombres='wegw gwgwe',
            password='12345'
    )

    assert user.is_superuser

@pytest.mark.django_db
def test_staff_user_creation():
    user = Usuario.objects.create_user(
            username='dasdas',
            email='asdasda@gmail.com',
            nombres='wegw gwgwe',
            password='12345',
            is_staff=False
    )

    assert user.is_staff

Pruebas Unitarias con Django


No sólo podemos utilizar Pytest para realizar nuestras pruebas, si estás utilizando Django en tu proyecto, puedes utilizar las herramientas que trae incorporadas, como TestCase.

TestCase es una subclase de Unittest, paquete nativo de Python para realizar Pruebas Unitarias, este nos permite crear clases y dentro de ellas funciones las cuales representarán nuestras pruebas, para esto debemos saber que TestCase implementa un método llamado setUp() el cual permite definir las instancias iniciales o estructuras iniciales que se utilizarán a lo largo de todas nuestras pruebas y es que una de las herramientas que trae incorporado Django en su paquete test es la simulación de un Cliente para pruebas, es decir, una simulación de un navegador que puede realizar peticiones a nuestras rutas para que podamos comprobar el funcionamiento de las vistas asociadas a estas rutas.

Adicionalmente a esto podemos utilizar librerías de terceros para complementar la creación de nuestras instancias de pruebas y datos de prueba, por ejemplo podemos utilizar la librería Factory Boy la cual nos permite generar clases con datos previos para simplemente generar instancias de estas clases y así simular los datos de nuestros modelos, por ejemplo, si se quisiera un Factory para el modelo Usuario:

import factory
from apps.usuario.models import Usuario

class UsuarioComunFactory(factory.Factory):
    class Meta:
        model = Usuario
    
    nombres = "Oliver"
    username = "oliver"
    email = "oliver@gmail.com"
    is_staff = False

class UsuarioAdminFactory(factory.Factory):
    class Meta:
        model = Usuario
    
    nombres = "Oliver"
    username = "oliver"
    is_staff = True
    is_superuser = True

De esta forma podemos utilizar estos Factorys y crear instancias de ellos en el método setUp() para que se puedan utilizar en cualquiera de las pruebas que pertenezcan a la clase.

from django.test import TestCase, Client
from tests.factories import UsuarioAdminFactory, UsuarioComunFactory

class UsuarioTestCase(TestCase):

    def setUp(self):
        self.client = Client()
        self.common_user = UsuarioComunFactory.create()
        self.superuser = UsuarioAdminFactory.create()

Si ahora le agregamos las pruebas que vimos al inicio del post, nuestro código quedaría de la siguiente manera:

from django.test import TestCase, Client
from tests.factories import UsuarioAdminFactory, UsuarioComunFactory

class UsuarioTestCase(TestCase):

    def setUp(self):
        self.client = Client()
        self.common_user = UsuarioComunFactory.create()
        self.superuser = UsuarioAdminFactory.create()
    
    def test_common_user_creation(self):
        self.assertEqual(self.common_user.is_active, True)
        self.assertEqual(self.common_user.is_staff, False)
        self.assertEqual(self.common_user.is_superuser, False)
    
    def test_suerpuser_creation(self):
        self.assertEqual(self.superuser.is_staff, True)
        self.assertEqual(self.superuser.is_superuser, True)

Ahora imaginemos que tenemos una vista de Login y queremos realizar una prueba para saber su funcionamiento, con TestCase y Client esto es posible, primero veamos la vista de Login.

from django.utils.decorators import method_decorator
from django.views.decorators.cache import never_cache
from django.views.decorators.csrf import csrf_protect
from django.contrib.auth import login

class Login(FormView):
    template_name = 'login.html'
    form_class = FormularioLogin
    success_url = reverse_lazy('index')

    @method_decorator(csrf_protect)
    @method_decorator(never_cache)
    def dispatch(self, request, *args, **kwargs):
        if request.user.is_authenticated:
            return HttpResponseRedirect(self.get_success_url())
        else:
            return super(Login, self).dispatch(request, *args, **kwargs)

    def form_valid(self, form):
        login(self.request, form.get_user())
        return super(Login, self).form_valid(form)

Ahora podemos crear nuestra prueba utilizando Cliente, debemos tener en cuenta que si estamos utilizando el sistema de Autenticación que Django trae por defecto(como es nuestro caso) podemos utilizar directamente una función llamada login() que viene incorporada dentro de Client().

def test_login(self):
        self.common_user.set_password('oliver')
        self.common_user.save()
        response = self.client.login(username='oliver', password='oliver')
        self.assertEquals(response, True)

    def test_login_fail(self):
        self.common_user.set_password('oliver')
        self.common_user.save()
        response = self.client.login(username='oliver', password='oliver1')
        self.assertEquals(response, False)

Y finalmente que pasaría si quisiera probar una ruta de listado de datos, ruta que sería del tipo GET, pues para ello primero crearemos una Vista que listará todos los usuarios registrados, donde:

  • LoginYSuperStaffMixin: valida que el usuario sea de tipo STAFF o SUPERUSUARIO para dejarlo pasar.
  • ValidarPermisosMixin: valida que el usuario tenga asignados los PERMISOS solicitados por la vista.
  • .is_ajax(): verifica que la petición sea realizada con una cabecera del tipo XMLHttpRequest.
class ListadoUsuario(LoginYSuperStaffMixin, ValidarPermisosMixin, ListView):
    model = Usuario
    permission_required = ('usuario.view_usuario', 'usuario.add_usuario',
                           'usuario.delete_usuario', 'usuario.change_usuario')

    def get_queryset(self):
        return self.model.objects.filter(is_active=True)
    
    def get(self,request,*args,**kwargs):
        if request.is_ajax():
            return HttpResponse(serialize('json', self.get_queryset()), 'application/json')
        else:
            return redirect('usuarios:inicio_usuarios')

De esta forma el código de nuestra prueba que evaluaría si la petición realizadaes correcta y si el número de usuarios registrados es 1 quedaría de la siguiente manera.

Hay que aclarar que cuando se realiza un client.login(), se crea una sesión para esa instancia de TestCase por lo que todas las pruebas que se ejecuten después se realizarán con un usuario autenticado.

def test_users_list(self):
        self.superuser.set_password('oliver')
        self.superuser.save()
        self.client.login(username='oliver', password='oliver')
        response = self.client.get('/usuarios/listado_usuarios/', 
                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEquals(response.status_code, 200)
        self.assertEquals(len(response.json()), 1)

Así, toda nuestra clase llamada UsuarioTestCase quedaría de la siguiente manera.

from django.test import TestCase, Client
from tests.factories import UsuarioAdminFactory, UsuarioComunFactory

class UsuarioTestCase(TestCase):

    def setUp(self):
        self.client = Client()
        self.common_user = UsuarioComunFactory.create()
        self.superuser = UsuarioAdminFactory.create()
    
    def test_common_user_creation(self):
        self.assertEqual(self.common_user.is_active, True)
        self.assertEqual(self.common_user.is_staff, False)
        self.assertEqual(self.common_user.is_superuser, False)
    
    def test_suerpuser_creation(self):
        self.assertEqual(self.superuser.is_staff, True)
        self.assertEqual(self.superuser.is_superuser, True)

    def test_login(self):
        self.common_user.set_password('oliver')
        self.common_user.save()
        response = self.client.login(username='oliver', password='oliver')
        self.assertEquals(response, True)

    def test_login_fail(self):
        self.common_user.set_password('oliver')
        self.common_user.save()
        response = self.client.login(username='oliver', password='oliver1')
        self.assertEquals(response, False)

    def test_users_list(self):
        self.superuser.set_password('oliver')
        self.superuser.save()
        self.client.login(username='oliver', password='oliver')
        response = self.client.get('/usuarios/listado_usuarios/', 
                                    HTTP_X_REQUESTED_WITH='XMLHttpRequest')
        self.assertEquals(response.status_code, 200)
        self.assertEquals(len(response.json()), 1)

Si quieres aprender a realizar Pruebas Unitarias con Python y Django de forma mas detallada, puedes revisar la siguiente lista de reproducción de nuestro canal de Youtube.

Otros Posts


Convertir Instancia de un Modelo de Django a un Diccionario
12 de Febrero de 2022 • Oliver Sandoval
Diferentes formas de convertir una instancia de un Modelo de Django a un Diccionario
Leer mas »
Configurar Multiples Bases de Datos con Django
21 de Octubre de 2021 • Oliver Sandoval
Multiples Bases de Datos con Django
Leer mas »
Autenticación en Django Rest Framework
4 de Septiembre de 2021 • Oliver Sandoval
Autenticacion en Django Rest Framework
Leer mas »