Saltar al contenido principal
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.

30 min de lectura
3 retos prácticos
1 caso real
Básico

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 existenteRefactorizas con total confianza
Bugs descubiertos en producciónBugs detectados antes del deploy
Horas depurando errores misteriososEl test te dice exactamente qué falló
"Creo que esto funciona...""SÉ que esto funciona"

La Pirámide de Testing

E2E Tests — Selenium, navegador real
Pocos · Lentos · Costosos
Integration Tests — Vistas, APIs, BD
Medianos · Velocidad moderada
Unit Tests — Modelos, funciones, lógica
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
DIAGRAMA
# 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
Python
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
BASH
# 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
OUTPUT
# ✅ 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) en tests.py
  • 2test_suma: verifica que 15 + 27 == 42
  • 3test_resta: verifica que 100 - 35 == 65
  • 4test_multiplicacion: verifica que 6 * 7 == 42
  • 5test_division: verifica que 100 / 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)
  • 2test_descuento_10_porciento: precio=100, pct=10 → 90.0
  • 3test_descuento_50_porciento: precio=200, pct=50 → 100.0
  • 4test_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 lanza ValueError("No se puede dividir entre cero") si b == 0
  • 2test_division_normal: dividir(10, 2) == 5.0
  • 3test_division_entre_cero: lanza ValueError al llamar dividir(10, 0)
  • 4test_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
Python
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
OUTPUT
$ 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, assertTrue y assertFalse
  • Usar assertIn, assertIsNone y assertRaises
  • Ejecutar tests con python manage.py test y 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