DÍA 01 — NIVEL BÁSICO
¿Qué son los tests
y por qué usarlos?
Aprende desde cero qué es el testing, cómo funciona internamente en Django, y escribe tu primer test funcional con asserts reales.
Teoría del Día
// conceptos fundamentales
¿Qué es un test?
Un test es código que verifica que tu código funciona correctamente. Es como un inspector de calidad automático que trabaja 24/7 sin cansarse.
Imagina que tienes una tienda online. Cada vez que agregas una nueva función — descuentos, cupones, pagos — ¿cómo sabes que no rompiste algo que ya funcionaba? Los tests te lo dicen en segundos.
¿Por qué son importantes?
| ❌ Sin tests | ✅ Con tests |
|---|---|
| "Funciona en mi máquina" | Funciona en todos los entornos |
| Miedo a cambiar código existente | Refactorizas con total confianza |
| Bugs descubiertos en producción | Bugs detectados antes del deploy |
| Horas depurando errores misteriosos | El test te dice exactamente qué falló |
| "Creo que esto funciona..." | "SÉ que esto funciona" |
La Pirámide de Testing
Pocos · Lentos · Costosos
Medianos · Velocidad moderada
Muchos · Rápidos · Baratos ← Empezamos aquí
¿Tu BD real está a salvo?
Django crea una base de datos temporal solo para tests. Al terminar, la destruye completamente. Tu BD de desarrollo o producción nunca se toca.
flujo-interno.txt
# El flujo completo de Django testing: # 1. Django lee tus clases de test # 2. Crea una BD temporal (test_nombrebd) # 3. Ejecuta cada test de forma AISLADA # 4. Muestra resultados: ✅ ok / ❌ FAIL # 5. Destruye la BD temporal Tu código test → TestRunner → BD temporal → Resultado → BD eliminada
Demostración en Vivo
// código completo y funcional
Tu primer test en Django
Cada test vive en tests.py dentro de tu app. La estructura es siempre la misma:
productos/tests.py
from django.test import TestCase # ↑ Clase base de Django — nos da BD temporal + métodos de verificación class MiPrimerTest(TestCase): # ↑ SIEMPRE hereda de TestCase # ↑ Agrupa tests relacionados en una misma clase def test_suma_basica(self): # ↑ Prefijo "test_" es OBLIGATORIO — sin él Django lo ignora resultado = 2 + 2 self.assertEqual(resultado, 4) # ↑ Si son iguales → ✅ PASA # ↑ Si son distintos → ❌ FALLA con mensaje exacto def test_falla_a_proposito(self): # Para ver cómo luce un fallo en el output resultado = 2 + 2 self.assertEqual(resultado, 5) # ↑ 4 != 5 → veremos el error en consola
Los asserts más usados
assertEqual(a, b)
Verifica que dos valores son exactamente iguales.
self.assertEqual(2 + 2, 4) ✅
assertTrue(x)
Verifica que la expresión es verdadera.
self.assertTrue(5 > 3) ✅
assertFalse(x)
Verifica que la expresión es falsa.
self.assertFalse(5 < 3) ✅
assertIn(a, b)
Verifica que el elemento está en la colección.
self.assertIn('pera', lista) ✅
assertIsNone(x)
Verifica que el valor es exactamente None.
self.assertIsNone(None) ✅
assertRaises(Error)
Verifica que se lanza una excepción específica.
with self.assertRaises(ValueError): ...
terminal
# Todos los tests del proyecto python manage.py test # Solo una app python manage.py test productos # Una clase específica python manage.py test productos.tests.MiPrimerTest # Un test específico python manage.py test productos.tests.MiPrimerTest.test_suma_basica # Con más detalle python manage.py test --verbosity=2
output — resultado
# ✅ Cuando TODO pasa Creating test database for alias 'default'... test_suma_basica (productos.tests.MiPrimerTest) ... ok Ran 1 test in 0.003s OK Destroying test database for alias 'default'... # ❌ Cuando algo FALLA FAIL: test_falla_a_proposito (productos.tests.MiPrimerTest) AssertionError: 4 != 5 # ↑ Te dice exactamente qué esperabas vs qué obtuviste
3 errores del Día 1 a evitar
Olvidar heredar de
TestCase · Nombrar métodos sin prefijo test_ · Poner múltiples asserts en un solo test. Cada test debe verificar una sola cosa.
Retos del Día
// practica lo aprendido
RETO 01
★☆☆
Tu primera clase de tests
Crear una clase con 4 tests básicos que todos pasen en verde.
- 1Crea
CalculadoraTest(TestCase)entests.py - 2
test_suma: verifica que15 + 27 == 42 - 3
test_resta: verifica que100 - 35 == 65 - 4
test_multiplicacion: verifica que6 * 7 == 42 - 5
test_division: verifica que100 / 4 == 25.0 - 6Ejecuta y confirma que el output dice OK
Pista: Usa
self.assertEqual() en cada test. El resultado de 100/4 es float: 25.0
RETO 02
★★☆
Validando lógica de negocio
Testear una función real de e-commerce — cálculo de descuentos.
- 1Crea:
def calcular_descuento(precio, pct): return precio - (precio * pct / 100) - 2
test_descuento_10_porciento: precio=100, pct=10 → 90.0 - 3
test_descuento_50_porciento: precio=200, pct=50 → 100.0 - 4
test_sin_descuento: precio=150, pct=0 → 150.0
Pista: Llama la función dentro de cada test y verifica el resultado con
assertEqual
RETO 03
★★★
Detectando errores con assertRaises
Verificar que tu código falla correctamente cuando debe fallar.
- 1Crea
dividir(a, b)que lanzaValueError("No se puede dividir entre cero")sib == 0 - 2
test_division_normal:dividir(10, 2) == 5.0 - 3
test_division_entre_cero: lanzaValueErroral llamardividir(10, 0) - 4
test_mensaje_error: el mensaje del error contiene la palabra"cero"
Pista:
with self.assertRaises(ValueError) as ctx: dividir(10,0) → luego self.assertIn("cero", str(ctx.exception))Caso Práctico Real
// aplicando lo aprendido en el mundo real
Startup RapidoYa — Bug en producción
// delivery · precios · descuentos
Contexto
Trabajas en RapidoYa, una startup de delivery. El equipo descubrió un bug en producción: los precios con descuento salen negativos cuando el porcentaje supera el 100%. El CTO pide que nunca más llegue un bug así a producción.
Requerimiento
El sistema debe garantizar que el precio final nunca sea negativo y que los descuentos mayores al 100% sean explícitamente rechazados con un error descriptivo.
Tu Tarea
Escribe tests que validen: ① un descuento válido calcula bien el precio · ② un descuento del 100% da precio = 0 · ③ descuento > 100% lanza ValueError · ④ descuento negativo lanza ValueError.
productos/tests.py — Solución guiada
from django.test import TestCase def calcular_precio_final(precio_original, descuento_pct): """Calcula precio con descuento. Rango válido: 0-100.""" if descuento_pct < 0 or descuento_pct > 100: raise ValueError( f"Descuento inválido: {descuento_pct}. Debe ser 0-100." ) return precio_original * (1 - descuento_pct / 100) class PrecioFinalRapidoYaTest(TestCase): def test_descuento_valido_calcula_correctamente(self): # $500 con 20% de descuento → $400 precio = calcular_precio_final(500, 20) self.assertEqual(precio, 400.0) def test_descuento_100_da_precio_cero(self): precio = calcular_precio_final(300, 100) self.assertEqual(precio, 0.0) def test_sin_descuento_precio_no_cambia(self): precio = calcular_precio_final(250, 0) self.assertEqual(precio, 250.0) def test_descuento_mayor_100_lanza_error(self): # ← el bug que llegó a producción with self.assertRaises(ValueError): calcular_precio_final(100, 150) def test_descuento_negativo_lanza_error(self): with self.assertRaises(ValueError): calcular_precio_final(100, -10) def test_mensaje_error_contiene_valor_recibido(self): with self.assertRaises(ValueError) as ctx: calcular_precio_final(100, 200) self.assertIn("200", str(ctx.exception))
terminal — resultado final
$ python manage.py test productos --verbosity=2 test_descuento_100_da_precio_cero ............ ok test_descuento_mayor_100_lanza_error ......... ok test_descuento_negativo_lanza_error .......... ok test_descuento_valido_calcula_correctamente .. ok test_mensaje_error_contiene_valor_recibido ... ok test_sin_descuento_precio_no_cambia .......... ok Ran 6 tests in 0.001s OK 🎉 — Bug de producción eliminado
Checklist de Aprendizaje
// marca lo que ya dominas
Haz clic en cada casilla para marcarla. Tu progreso se guarda automáticamente.
- Explicar qué es un test y por qué es necesario escribirlos
- Conocer la pirámide de testing: unit, integration y e2e
- Crear una clase de test heredando correctamente de
TestCase - Nombrar métodos con prefijo
test_y entender por qué importa - Usar
assertEqual,assertTrueyassertFalse - Usar
assertIn,assertIsNoneyassertRaises - Ejecutar tests con
python manage.py testy sus variantes - Leer e interpretar el output de tests (verde y rojo)
- Entender que Django usa una BD temporal — tu BD real nunca se toca