Saltar al contenido

Lección 16 de 25 · Lectura y práctica

Escribir Tests Unitarios e Integración con Pytest y FastAPI TestClient

Concepto claveLos tests unitarios e integración son el sistema inmunológico de tu API. Imagina que cada endpoint es un órgano vital: los tests unitarios verifican que cada célula (función individual) funcione correctamente,.

Del concepto a la práctica

Taller de esta lección

Una suite equilibrada separa la regla de negocio del transporte HTTP. Una función que calcula si un artículo puede publicarse se prueba con datos simples; TestClient comprueba que la ruta traduzca esa decisión a código y cuerpo. Las fixtures deben aislar estado para que el orden no cambie el resultado.

Ejemplo resuelto

Con title="Guía" y body no vacío, la función devuelve verdadero y el endpoint responde 201. Con body="", la función devuelve falso antes de llamar al repositorio; una prueba que comprueba ese falso debe pasar, no quedar fallida y la integración responde 422.

Pruébalo

  1. Escribe dos pruebas para la función pura.

  2. Crea una fixture que construya la aplicación.

  3. Sustituye el repositorio por uno en memoria.

  4. Comprueba código y número de escrituras.

Comprueba tu resultado

Debes obtener dos éxitos unitarios, 201 para el caso válido y cero escrituras para el inválido.

Error frecuente: compartir una lista mutable entre pruebas. Corrígelo creando el estado dentro de cada fixture y verifica que cada test parte vacío.

Navegador, API y base de datos conectados por solicitudes, consultas y respuestas en sentidos opuestos.
CursaloMapa de práctica
Del navegador a los datos

El navegador envía una solicitud a la API. El servidor valida la operación y consulta los datos; después devuelve una respuesta que el navegador puede mostrar. La base de datos se comunica con el servidor y no directamente con el navegador.

Ver la infografía en tamaño completo ↗

Concepto clave

Los tests unitarios e integración son el sistema inmunológico de tu API. Imagina que cada endpoint es un órgano vital: los tests unitarios verifican que cada célula (función individual) funcione correctamente, mientras que los tests de integración aseguran que todos los órganos trabajen en armonía. En el desarrollo backend profesional, escribir tests no es opcional; es lo que separa un prototipo de un sistema en producción.

Con FastAPI, tenemos dos herramientas principales: Pytest para la infraestructura de testing y TestClient para simular peticiones HTTP sin levantar un servidor real. La magia está en que TestClient se integra directamente con tu aplicación FastAPI, permitiéndote probar rutas, dependencias, middlewares y validaciones como si estuvieras haciendo peticiones reales, pero en milisegundos y de forma aislada.

Cómo funciona en la práctica

El flujo de trabajo profesional sigue estos pasos:

  1. Configurar un entorno de testing separado (usualmente con una base de datos en memoria o contenedores Docker)
  2. Crear fixtures de Pytest para datos de prueba reutilizables
  3. Escribir tests unitarios para funciones puras (lógica de negocio, validadores, utilidades)
  4. Escribir tests de integración para endpoints completos, incluyendo autenticación y dependencias
  5. Ejecutar tests automáticamente en CI/CD con cobertura mínima del 80%

Un patrón común es la pirámide de testing: muchos tests unitarios rápidos (base), algunos tests de integración (medio) y pocos tests end-to-end (cima). Para APIs con FastAPI, nos enfocamos en los dos primeros niveles.

Código en acción

Veamos un ejemplo completo. Primero, una función de negocio que necesita tests unitarios:

# app/utils/calculations.py
def calculate_discount(price: float, user_tier: str) -> float:
    """Calcula descuento basado en precio y categoría de usuario"""
    if user_tier == "premium":
        return price * 0.8  # 20% descuento
    elif user_tier == "regular" and price > 100:
        return price * 0.9  # 10% descuento para compras mayores a 100
    return price  # Sin descuento

Ahora, el test unitario correspondiente:

# tests/unit/test_calculations.py
import pytest
from app.utils.calculations import calculate_discount

def test_calculate_discount_premium():
    """Test: usuario premium recibe 20% de descuento siempre"""
    result = calculate_discount(200.0, "premium")
    assert result == 160.0  # 200 * 0.8

def test_calculate_discount_regular_large_purchase():
    """Test: usuario regular con compra >100 recibe 10% descuento"""
    result = calculate_discount(150.0, "regular")
    assert result == 135.0  # 150 * 0.9

def test_calculate_discount_no_discount():
    """Test: usuario regular con compra pequeña no recibe descuento"""
    result = calculate_discount(50.0, "regular")
    assert result == 50.0

# Test con parametrización (patrón profesional)
@pytest.mark.parametrize("price,user_tier,expected", [
    (200, "premium", 160),
    (150, "regular", 135),
    (50, "regular", 50),
    (80, "premium", 64),
])
def test_calculate_discount_parametrized(price, user_tier, expected):
    """Test múltiples casos en una sola función"""
    result = calculate_discount(float(price), user_tier)
    assert result == expected

Errores comunes

  • Testear implementación en lugar de comportamiento: Escribir tests que dependen de detalles internos en lugar de lo que la función debería hacer. Si refactorizas, los tests se rompen aunque el comportamiento sea correcto.
  • No aislar tests de integración: Usar la misma base de datos de desarrollo para tests, contaminando datos entre ejecuciones. Usa bases de datos en memoria (SQLite) o contenedores efímeros.
  • Olvidar resetear el estado entre tests: Si un test modifica variables globales o estado compartido, puede afectar tests siguientes. Usa fixtures de Pytest con scope="function".
  • Tests demasiado específicos: Assert que comparan strings completos o estructuras exactas en lugar de verificar los aspectos clave. Esto hace los tests frágiles ante cambios menores.
  • No probar casos edge: Solo testear el "happy path". Los bugs suelen aparecer en entradas inesperadas, valores límite o errores de red.

Checklist de dominio

  • ✓ Puedo escribir tests unitarios para funciones puras usando pytest.mark.parametrize
  • ✓ Configuro TestClient correctamente, sobrescribiendo dependencias para testing
  • ✓ Uso fixtures de Pytest para crear datos de prueba reutilizables y aislados
  • ✓ Pruebo respuestas HTTP, códigos de estado, schemas de respuesta y errores
  • ✓ Mido cobertura de código y mantengo mínimo 80% en lógica de negocio
  • ✓ Integro tests en pipeline CI/CD que falla si no pasan
  • ✓ Escribo tests para autenticación, autorización y middlewares

Implementar tests para API de gestión de productos con autenticación

En este ejercicio práctico, implementarás tests completos para una API REST de productos que incluye autenticación JWT. Sigue estos pasos:

  1. Configuración inicial: Crea un archivo tests/conftest.py con fixtures para:
    • TestClient con app FastAPI
    • Base de datos SQLite en memoria para testing
    • Usuario de prueba con token JWT válido
  2. Tests unitarios: Escribe tests para:
    • Función que valida precio de producto (debe ser positivo)
    • Función que formatea respuesta de productos
    Usa parametrización para casos válidos e inválidos.
  3. Tests de integración: Usando TestClient, prueba:
    • GET /products sin autenticación (debe devolver 401)
    • GET /products con token válido (debe devolver lista)
    • POST /products con datos válidos (crea producto)
    • POST /products con precio negativo (debe devolver 422)
    • DELETE /products/{id} con usuario no autorizado (debe devolver 403)
  4. Refactorización: Identica código duplicado en los tests y extraelo a fixtures o funciones helper.
  5. Ejecución: Corre tests con pytest -v --cov=app --cov-report=html y verifica cobertura >85%.
Pistas
  • Usa pytest.fixture para crear un token JWT de prueba que expire en 30 minutos
  • Para testear respuestas de error, verifica tanto el status_code como el JSON de respuesta
  • Crea una fixture que limpie la base de datos después de cada test usando yield

Evalua tu comprension

Completa el quiz interactivo de arriba para ganar XP.

Sigue con Dominio Profesional de FastAPI: Construcción de APIs Escalables y Seguras

Esta lección forma parte de un curso de Cursalo. Revisa su programa y elige entre la compra individual o el acceso a toda la escuela.

Si ya tenías acceso a este curso, entra con tu cuenta: tus derechos y tu progreso se conservan. No necesitas comprarlo de nuevo.

01 / ELEGIR UNA RUTA

El próximo paso

Aprende IA mientras construyes algo que sí necesitas.

Empieza por el curso inicial o explora el catálogo para encontrar una práctica alineada con tu trabajo.