Framework de testing de Django
Django extiende unittest.TestCase con su propia clase TestCase que proporciona:

- Una base de datos de test aislada que se recrea en cada método de test.
- Fixtures para cargar datos iniciales.
- Assertions especializadas para Django.
- Un cliente HTTP simulado para testar vistas.
Estructura básica de un test
# catalogo/tests.py o catalogo/tests/test_models.py
from django.test import TestCase
from django.utils import timezone
from .models import Producto, Categoria
class ProductoModelTest(TestCase):
"""Tests del modelo Producto."""
@classmethod
def setUpTestData(cls):
"""
Se ejecuta una vez para toda la clase de tests (más eficiente que setUp).
No modificar los objetos creados aquí en los tests individuales.
"""
cls.categoria = Categoria.objects.create(
nombre='Electrónica',
slug='electronica'
)
def setUp(self):
"""Se ejecuta antes de cada método de test individual."""
self.producto = Producto.objects.create(
nombre='Laptop Test',
precio=999.99,
categoria=self.categoria,
activo=True
)
def tearDown(self):
"""Se ejecuta después de cada test (raramente necesario con TestCase)."""
pass
def test_str_devuelve_nombre(self):
"""__str__ debe devolver el nombre del producto."""
self.assertEqual(str(self.producto), 'Laptop Test')
def test_precio_positivo(self):
"""El precio debe ser positivo."""
self.assertGreater(self.producto.precio, 0)
def test_producto_activo_por_defecto(self):
"""Los productos se crean activos por defecto."""
producto = Producto.objects.create(
nombre='Nuevo',
precio=10.00,
categoria=self.categoria
)
self.assertTrue(producto.activo)
def test_get_absolute_url(self):
"""get_absolute_url debe devolver la URL correcta."""
url = self.producto.get_absolute_url()
self.assertIn(str(self.producto.pk), url)
def test_manager_activos_filtra_inactivos(self):
"""El manager 'activos' no debe devolver productos inactivos."""
self.producto.activo = False
self.producto.save()
activos = Producto.activos.all()
self.assertNotIn(self.producto, activos)
Tests de formularios
from django.test import TestCase
from .forms import ProductoForm, RegistroForm
class ProductoFormTest(TestCase):
@classmethod
def setUpTestData(cls):
cls.categoria = Categoria.objects.create(nombre='Test', slug='test')
def test_formulario_valido(self):
datos = {
'nombre': 'Producto válido',
'precio': '29.99',
'categoria': self.categoria.pk,
}
form = ProductoForm(data=datos)
self.assertTrue(form.is_valid())
def test_nombre_muy_corto_invalido(self):
datos = {
'nombre': 'A', # Muy corto
'precio': '29.99',
'categoria': self.categoria.pk,
}
form = ProductoForm(data=datos)
self.assertFalse(form.is_valid())
self.assertIn('nombre', form.errors)
def test_precio_negativo_invalido(self):
datos = {
'nombre': 'Producto test',
'precio': '-5.00',
'categoria': self.categoria.pk,
}
form = ProductoForm(data=datos)
self.assertFalse(form.is_valid())
self.assertIn('precio', form.errors)
def test_campos_obligatorios(self):
form = ProductoForm(data={})
self.assertFalse(form.is_valid())
self.assertIn('nombre', form.errors)
self.assertIn('precio', form.errors)
Tests de señales
from unittest.mock import patch, MagicMock
from django.test import TestCase
from django.contrib.auth import get_user_model
User = get_user_model()
class PerfilSignalTest(TestCase):
def test_perfil_se_crea_al_registrar_usuario(self):
"""Verificar que post_save crea el perfil automáticamente."""
usuario = User.objects.create_user(
username='test_signal',
email='test@test.com',
password='pass123'
)
self.assertTrue(hasattr(usuario, 'perfil'))
self.assertIsNotNone(usuario.perfil)
def test_perfil_no_se_duplica_al_actualizar(self):
"""Actualizar el usuario no debe crear un perfil duplicado."""
from usuarios.models import Perfil
usuario = User.objects.create_user(username='test_dup', password='pass')
usuario.first_name = 'Ana'
usuario.save()
perfiles = Perfil.objects.filter(usuario=usuario)
self.assertEqual(perfiles.count(), 1)
Tests de managers
class ProductoManagerTest(TestCase):
def setUp(self):
cat = Categoria.objects.create(nombre='Cat', slug='cat')
Producto.objects.create(nombre='Activo 1', precio=10, categoria=cat, activo=True)
Producto.objects.create(nombre='Activo 2', precio=20, categoria=cat, activo=True)
Producto.objects.create(nombre='Inactivo', precio=5, categoria=cat, activo=False)
def test_manager_activos_count(self):
self.assertEqual(Producto.activos.count(), 2)
def test_manager_en_oferta(self):
cat = Categoria.objects.first()
Producto.objects.create(
nombre='Oferta',
precio=100,
precio_oferta=80,
categoria=cat,
activo=True
)
self.assertEqual(Producto.objects.en_oferta().count(), 1)
Ejecutar los tests
# Ejecutar todos los tests
python manage.py test
# Ejecutar tests de una aplicación
python manage.py test catalogo
# Ejecutar una clase de tests específica
python manage.py test catalogo.tests.ProductoModelTest
# Ejecutar un test específico
python manage.py test catalogo.tests.ProductoModelTest.test_str_devuelve_nombre
# Ejecutar con verbosidad alta
python manage.py test --verbosity 2
# Mantener la base de datos de test (más rápido en ejecuciones repetidas)
python manage.py test --keepdb
Alan Sastre
Ingeniero de Software y formador, CEO en CertiDevs
Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Django es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.
Más tutoriales de Django
Explora más contenido relacionado con Django y continúa aprendiendo con nuestros tutoriales gratuitos.
Aprendizajes de esta lección
Crear tests unitarios con la clase TestCase de Django. Usar setUp() y tearDown() para preparar y limpiar el estado de los tests. Testear la lógica de negocio de los modelos Django. Verificar la validación de formularios en los tests. Testear señales y managers personalizados con assertions específicos.