Pruebas Unitarias en Django: Lo que debes de conocer

- Andrés Cruz

In english

Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos Curso y libro desarrollo web con Django 5 y Python 3 + integración con Vue 3, Bootstrap y Alpine.js.

De momento, hemos realizado algunos métodos de prueba y hay dos factores que tenemos que tener en cuenta, si hacemos peticiones mediante el Client(), por ejemplo:

client = Client()
response = client.get("/")

O creando instancias de modelos u otras clases en la prueba:

Comment.objects.create(text='text 1')

Las conexiones se realizan en el contexto de la aplicación en desarrollo según lo que tenemos configurado en la prueba:

import unittest.TestCase

O en memoria:

from django.test import TestCase

Esto es fácilmente probable si hacemos algunas pruebas; si desde una prueba, intentas realizar alguna conexión a la base de datos empleando el cliente o una instancia de un modelo:

import unittest
from django.test import Client

from .models import Comment
class ContactTests(unittest.TestCase):
    def test_index(self):
        print(Comment.objects.all())
        client = Client()
        response = client.get("/")
        print(response)
        ***

Y ejecutamos:

$ python manage.py test

Veremos que devuelve un listado con los datos que tengamos en la base de datos de desarrollo, pero, si lo configuramos con el TestCase de Django:

from django.test import Client, TestCase

from .models import Comment
class ContactTests(TestCase):
    def test_index(self):
        print(Comment.objects.all())
        client = Client()
        response = client.get("/")
        print(response)
        ***

Veremos que devuelve un resultado vacío de comentarios, aunque tengamos comentarios en la base de datos de desarrollo.

En ambos casos, para obtener al menos la estructura de la base de datos, se emplea la que tengamos configurada:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
    }
}

Para entender el funcionamiento de las pruebas, vamos a configurar la base de datos para el ambiente de pruebas:

DATABASES = {
    'default': {
        'ENGINE': 'django.db.backends.sqlite3',
        'NAME': BASE_DIR / 'db.sqlite3',
        'TEST': {
            'NAME': BASE_DIR / 'db.sqlite3',
         },
    }
}

Antes de ejecutar, se recomienda al lector que haga una copia de la base de datos SQLite (<your-project>\db.sqlite3) para que luego la puedas restaurar, ya que, la misma al terminar la prueba será eliminada, finalmente, ejecutamos:

$ python manage.py test

Sin importar que clase TestCase estés empleando, ya que, es la misma base de datos que se está empleando para desarrollo, te preguntará si realmente la quieres eliminar:

Creating test database for alias 'default'...
Destroying old test database for alias 'default'...
Type 'yes' if you would like to try deleting the test database '/Users/andrescruz/Desktop/proyects/django/curso/django-base/db.sqlite3', or 'no' to cancel: 

Damos que sí, ya que tenemos la copia:

yes

Y veremos que la base de datos es destruida (si intentas ingresar a la aplicación http://127.0.0.1:8000, veras que devuelve un error de que no encuentra la tabla para la sesión, que es empleada internamente por Django antes de resolver las peticiones implementadas por nosotros en las vistas):

OK
Destroying test database for alias 'default'...

Como resultado de:

print(Comment.objects.all())

Veremos que, aunque tengamos datos en la base de datos de desarrollo, el listado aparece vacío (recordar, solamente si empleamos from django.test import TestCase que es el que siempre deberíamos de emplear):

<QuerySet []>

Las pruebas que requieren una base de datos, no utilizarán la base de datos "real" (de producción o desarrollo) del proyecto. Para las pruebas se crean bases de datos separadas y en blanco.

A partir de ahora, para hacer las pruebas, debes de emplear:

from django.test import TestCase

Y no la otra, que es provista por Python:

import unittest.TestCase

Ya que, la clase provista por Django (from django.test import TestCase) no confirma (commit) los datos que estemos manipulando en las pruebas unitarias y por defecto, tampoco emplea los registros almacenados en la base de datos, a diferencia del paquete de Python (import unittest.TestCase) que si confirma las operaciones.

En resumen, si realizas conexiones from django.test import TestCase se emplea el contexto de la aplicación configurada (que en nuestro caso sería el ambiente de desarrollo), pero si utilizas import unittest.TestCase, se emplean un entorno independiente provisto por Django para hacer pruebas.

Esto es imprescindible ya que, también con esto podemos conocer la esencia de las pruebas en el cual, nos permite crear nuestro entorno o ambiente controlado y es el mismo cada vez que ejecutamos las pruebas, suceden los siguientes pasos:

  1. Se construye el ambiente
  2. Nosotros como desarrollador, creamos los datos de prueba y demas instancia a emplear, para eso, podemos emplear el método de setUp():
from django.test import Client, TestCase
class ContactTests(TestCase):

    def setUp(self):
        Comment.objects.create(text='text 1')
        Comment.objects.create(text='text 2')
        self.comments = Comment.objects.all()

        self.client = Client()
  1. Crear las pruebas (el resto de los métodos en la clase).
  2. Automáticamente Django elimina el ambiente anterior, por lo tanto, para el ejemplo que definimos en el ejemplo anterior, que creamos 2 comentarios, los mismos son destruidos de manera automática al momento de terminar la prueba, lo cual es excelente ya que, siempre podemos tener un ambiente ideal y controlado totalmente por nosotros (incluyendo la cantidad de registros y su estructura), lo cual no podria ser asi si el ambiente no se destruyera).

Con lo comentado en este apartado, puedes tener una buena idea de cómo funcionan las pruebas ya aprovechar este conocimiento para crear las pruebas unitarias a medida.

Crud de Comentarios

En este apartado, veremos cómo realizar pruebas para el CRUD de los comentarios, los desarrollos realizados en este apartado, las puedes tomar como experimentos que nos servirán para entender las pruebas Django en base a lo experimentado en el apartado anterior.

Index

Ya que conocemos que, al momento de ejecutar las pruebas, no podemos tener acceso a los datos que tengamos definidos en el modo desarrollo, si no, los debemos de crear nosotros al momento de ejecutar la prueba en el ambiente de pruebas, vamos a configurar los datos iniciales de la siguiente manera:

comments/tests.py

# import unittest
from django.test import Client, TestCase

from .models import Comment
class ContactTests(TestCase):

    def setUp(self):
        Comment.objects.create(text='text 1')
        Comment.objects.create(text='text 2')
        self.comments = Comment.objects.all()
        print(self.comments)
        self.client = Client()

Como comentamos anteriormente, el método de setUp() se emplea para inicializar datos, para nuestra prueba sería el cliente y los datos de prueba que podremos emplear a lo largo de TODAS las pruebas para el CRUD de comentarios, es importante acotar que tenemos una paginación de dos niveles:

comments/views.py

 

def index(request):
    comments = Comment.objects.all()
    paginator = Paginator(comments,2)
    ***

Y es por eso que solamente creamos 2 comentarios, ya que, aparte de comprobar por el código de estado, es imprescindible que comprobemos sobre el contenido devuelto que en este ejemplo, serían los comentarios paginados listados en la tabla:

comments/tests.py

class ContactTests(TestCase):
    def test_index(self):
        response = self.client.get("/tasks/")
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,self.comments[0].text)
        self.assertContains(response,self.comments[1].text)

Puedes emplear un for, para iterar los comentarios, pero, para legibilidad del ejercicio, en el libro preferimos usarlo de la forma presentada. Con esto, estamos probando tanto el código de estado, que es lo primero que siempre deberíamos de comprobar y también parte del contenido mediante los métodos de tipo aserción.

Como comentamos antes, como quieras implementar las pruebas y que probar depende totalmente del desarrollador y el ejercicio anterior lo puedes adaptar a tus necesidades, también, por sencillez del ejercicio, no probamos la paginación en sí, o los registros que están en la paginación y es por eso que creamos solamente dos registros, ya que, si creas un tercero:

comments/tests.py

class ContactTests(TestCase):
    def setUp(self):
        Comment.objects.create(text='text 1')
        Comment.objects.create(text='text 2')
        Comment.objects.create(text='text 3')
        self.comments = Comment.objects.all()
        print(self.comments)
        self.client = Client()

    def test_index(self):
        response = self.client.get("/tasks/")
        self.assertEqual(response.status_code, 200)
        self.assertContains(response,self.comments[0].text)
        self.assertContains(response,self.comments[1].text)
        self.assertContains(response,self.comments[2].text)

Verás que al ejecutar la prueba, devuelve un error al no encontrar el tercer registro al tener en este ejemplo una paginación de dos niveles.

Finalmente ejecuta la prueba y evalúa el resultado:

$ python manage.py test
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz en Udemy

Acepto recibir anuncios de interes sobre este Blog.

!Cursos a!

10$

En Udemy

Quedan 3 días!

Udemy

!Cursos desde!

4$

En Academia

Ver los cursos

!Libros desde!

1$

Ver los libros
¡Hazte afiliado en Gumroad!
!Web Alojada en Hostinger!