Juan Diego Andrés PRADA··RAMÍREZ Entrar
Lección 6 de 7

Async, docs y testing

¿Qué es?

Este módulo reúne tres habilidades finales. Async (asíncrono) es una forma de programar para atender muchas peticiones a la vez mientras unas esperan respuestas externas. Docs es la documentación de tu API: la página interactiva que FastAPI genera sola para que otros entiendan y prueben tus endpoints. Testing es escribir pruebas automáticas que verifican que tu API se comporta como esperas, sin que tengas que revisarlo todo a mano.

¿Cómo funciona?

Con async def y await, un endpoint puede ceder el control mientras espera una operación de entrada/salida, y el servidor aprovecha para atender otras peticiones; para trabajo posterior a la respuesta existen las BackgroundTasks. La documentación se enriquece con metadatos como title, summary y tags, y se alimenta sola de tus tipos y modelos. Las pruebas usan TestClient, que llama a tu app sin levantar un servidor real, y comprueban con assert que cada respuesta tenga el estado y el contenido correctos.

¿Para qué sirve?

Estas tres piezas separan una API de juguete de una lista para usarse: la asincronía la hace rendir bajo carga, la documentación la vuelve usable por otros, y las pruebas avisan cuando algo se rompe. Juntas te dan confianza de que el sistema funciona y de que seguirá funcionando al cambiarlo. Con ellas entregarás tu proyecto final, «API para tus herramientas», sabiendo que corre de forma concurrente, se documenta sola y se prueba automáticamente.

Cierras el curso con tres habilidades que separan una API de juguete de una lista para usarse: ejecutar trabajo de forma asíncrona, documentar profesionalmente y probar automáticamente. Con esto entregarás tu «API para tus herramientas» con confianza de que funciona y de que otros sabrán usarla.

async/await: cuándo y por qué

FastAPI es asíncrono de raíz. Un endpoint puede ser normal (def) o asíncrono (async def). La diferencia importa cuando esperas operaciones de entrada/salida: una llamada HTTP a otro servicio, una consulta a base de datos asíncrona, leer un archivo grande.

import httpx
from fastapi import FastAPI

app = FastAPI()


@app.get("/clima/{ciudad}")
async def clima(ciudad: str):
 async with httpx.AsyncClient() as client:
 resp = await client.get(f"https://api.ejemplo.com/clima/{ciudad}")
 return resp.json()

Mientras una petición espera la respuesta externa (await), el servidor atiende otras. Eso es concurrencia: más peticiones simultáneas con los mismos recursos. La regla práctica: usa async def cuando dentro hagas await; usa def normal para código que no espera nada o que usa librerías síncronas.

Nunca pongas trabajo bloqueante dentro de un async def. Un time.sleep(5) o un cálculo pesado dentro de una corrutina congela todo el servidor para todos. Si tu función no usa await, decláralala con def normal: FastAPI la corre en un hilo aparte y no bloquea el bucle de eventos.

Para tareas que deben ocurrir después de responder (enviar un correo, escribir un log), usa BackgroundTasks:

from fastapi import BackgroundTasks


def registrar(mensaje: str):
 with open("log.txt", "a") as f:
 f.write(mensaje + "\n")


@app.post("/notas")
async def crear(valor: float, tareas: BackgroundTasks):
 tareas.add_task(registrar, f"Nota creada: {valor}")
 return {"valor": valor}

El cliente recibe la respuesta de inmediato y el log se escribe en segundo plano.

Documentación profesional

FastAPI genera /docs (Swagger UI) y /redoc solo, a partir de tus tipos. Tú la enriqueces con metadatos:

app = FastAPI(
 title="API para tus herramientas",
 description="Expone un calificador OMR y un sistema de notas.",
 version="1.0.0",
)


@app.post("/notas", summary="Registrar una nota", tags=["notas"])
def crear_nota(valor: float):
 """
 Crea una nota nueva.

 - **valor**: número de 0.0 a 5.0
 """
 return {"valor": valor}

El summary y el docstring aparecen en /docs; los tags agrupan endpoints por sección. Documentar respuestas de error también ayuda:

@app.get(
 "/notas/{nota_id}",
 responses={404: {"description": "Nota no encontrada"}},
)
def obtener(nota_id: int):
 ...

La mejor documentación es la que se escribe sola. Cada modelo Pydantic, cada response_model y cada tipo que declaras ya alimenta /docs. Invertir en buenos modelos te ahorra mantener documentación aparte que se desactualiza.

Testing con TestClient

FastAPI incluye un cliente de pruebas que llama a tu app sin levantar un servidor real. Se apoya en pytest:

pip install pytest httpx

Supongamos main.py con la app. Creas test_main.py:

from fastapi.testclient import TestClient
from main import app

client = TestClient(app)


def test_crear_nota():
 resp = client.post("/notas", json={"estudiante": "Ana", "valor": 4.5})
 assert resp.status_code == 201
 assert resp.json()["aprobada"] is True


def test_valor_invalido():
 resp = client.post("/notas", json={"estudiante": "Ana", "valor": 9})
 assert resp.status_code == 422


def test_nota_inexistente():
 resp = client.get("/notas/9999")
 assert resp.status_code == 404

Cada prueba envía una petición y verifica el resultado con assert. Corres todo con pytest. Fíjate en qué probamos: el camino feliz (201), la validación (422) y los errores (404). Una buena API se prueba por su comportamiento visible, no por su código interno.

Ejemplo guiado: del endpoint a la prueba

  1. Toma el POST /notas con response_model del módulo de Pydantic en main.py.
  2. Crea test_main.py con las tres pruebas de arriba.
  3. Corre pytest -v. Verás tres líneas en verde.
  4. Rompe a propósito el rango del modelo (pon le=10) y vuelve a correr: test_valor_invalido falla, porque ahora 9 se acepta. Eso es una prueba haciendo su trabajo: avisar cuando el comportamiento cambia.
  5. Repara el modelo y confirma que todo vuelve a verde.

Para probar endpoints protegidos, primero pides un token y lo mandas en la cabecera:

def test_crear_requiere_auth():
 assert client.post("/notas", json={"valor": 4.0}).status_code == 401

 login = client.post("/login", data={"username": "profe", "password": "clave123"})
 token = login.json()["access_token"]
 headers = {"Authorization": f"Bearer {token}"}
 assert client.post("/notas", json={"valor": 4.0}, headers=headers).status_code == 201

Tech English: concurrency = concurrencia (atender varias cosas a la vez). blocking = bloqueante. fixture = preparación reutilizable de una prueba en pytest. assertion = afirmación que la prueba verifica. coverage = cobertura del código probado.

Ejercicios

  1. Suite de pruebas del CRUD. Escribe pruebas con TestClient para tu API de notas que cubran: crear (201), leer existente (200), leer inexistente (404), validación fallida (422) y borrar (204). Evalúa: ¿cada prueba comprueba código de estado y contenido relevante? ¿se cubren los caminos de error, no solo el feliz?

  2. Calificador OMR como API documentada y probada. Diseña POST /calificar que reciba una hoja OMR y la clave de respuestas, y devuelva el puntaje. Documenta el endpoint con summary, tags y descripción de la respuesta 422; añade pruebas para una hoja perfecta, una a medias y una con una letra inválida. Evalúa el diseño completo: recursos claros, validación en el modelo, errores útiles y pruebas que defienden el comportamiento. Este es el corazón del proyecto final, «API para tus herramientas».

Tu progreso se guarda en este navegador. Inicia sesión para guardarlo en tu cuenta y verlo desde cualquier dispositivo.