Postman es la puerta de entrada de casi todo QA al testing de APIs. Pero Postman no encaja de forma nativa en un pipeline de CI/CD (hace falta Newman por encima) y cuando una API crece a decenas de endpoints, darle a "Send" test a test, a mano, deja de ser sostenible. Ese techo es real, y es también donde se separa un perfil de QA manual de uno de automatización.
pytest es el framework de testing más usado del ecosistema Python: el 52% de quienes respondieron qué usan para tests unitarios eligió pytest, frente al 25% de unittest, la librería estándar del lenguaje (Python Developers Survey 2023, JetBrains y Python Software Foundation). Combinado con la librería requests, convierte cualquier test de API que ya sabes hacer a mano en código que corre solo, en segundos, cada vez que hace falta.
Esta guía cubre la instalación, tu primer test de API real, fixtures para no repetir el setup, parametrize para probar varios casos con el mismo test, y las aserciones que de verdad importan. Si vienes de testear APIs con Postman, este es el siguiente paso lógico.
Por qué dar el salto de Postman a código
Porcentaje de desarrolladores Python que usa pytest frente a los que usan unittest (la librería estándar) para escribir tests unitarios.
Fuente: Python Developers Survey 2023, JetBrains y Python Software Foundation
Una colección de Postman documenta bien una API y es perfecta para explorarla o probar un endpoint puntual. Lo que no hace es integrarse en el ciclo de vida del código: no se revisa en un pull request como el resto del proyecto, no falla automáticamente un build si algo se rompe, y ejecutarla completa sigue dependiendo de que alguien abra la app y le dé al botón.
pytest resuelve justo eso. Es gratuito, open source, y un archivo de tests escrito con pytest es un archivo .py más: vive en el repositorio, se versiona con Git, se revisa en el pull request y corre solo en el pipeline de integración continua sin que nadie tenga que abrir nada a mano.
Instalación y tu primer test de API
Necesitas Python instalado (compruébalo con python --version) y dos paquetes: pytest, el framework de testing, y requests, la librería estándar de Python para hacer peticiones HTTP.
pip install pytest requestsVamos a testear una API pública real que no requiere clave de acceso, pensada precisamente para practicar: JSONPlaceholder. Este primer test comprueba que un usuario existe y que su respuesta trae un email con formato válido:
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
def test_get_user_devuelve_200():
response = requests.get(f"{BASE_URL}/users/1")
assert response.status_code == 200
def test_usuario_tiene_email_valido():
response = requests.get(f"{BASE_URL}/users/1")
data = response.json()
assert "email" in data
assert "@" in data["email"]Para ejecutarlo, en la misma carpeta que el archivo:
pytest -vpytest detecta automáticamente cualquier archivo que empiece por test_ y cualquier función que empiece por test_ dentro de él. No hace falta registrar nada a mano ni heredar de ninguna clase, a diferencia de unittest. Eso es justo lo que hace que sea tan directo de empezar a usar.
Fixtures: deja de repetir el setup en cada test
En el ejemplo anterior, la URL base se repite en cada función. En un archivo con veinte tests, eso es veinte sitios donde cambiar algo si la URL cambia. Una fixture resuelve esto: es una función que prepara algo que tus tests necesitan y se lo pasa por inyección de dependencias, sin que tengas que llamarla tú a mano.
import pytest
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
@pytest.fixture
def api_session():
session = requests.Session()
session.headers.update({"Accept": "application/json"})
yield session
session.close()
def test_usuario_existe(api_session):
response = api_session.get(f"{BASE_URL}/users/1")
assert response.status_code == 200pytest ve que test_usuario_existe pide un parámetro llamado api_session, busca una fixture con ese nombre y la ejecuta antes del test. Todo lo que va antes del yield es el setup (aquí, crear la sesión); todo lo que va después es el teardown, la limpieza que se ejecuta cuando el test termina (aquí, cerrar la sesión). Si mañana necesitas añadir un token de autenticación a cada petición, lo cambias en un único sitio.
Parametrize: un test, muchos casos
Testear solo el camino feliz (un usuario que existe) deja fuera los casos que de verdad rompen una API en producción. El decorador @pytest.mark.parametrize ejecuta el mismo test una vez por cada combinación de datos que le pases, sin duplicar código:
import pytest
import requests
BASE_URL = "https://jsonplaceholder.typicode.com"
@pytest.mark.parametrize("user_id, status_esperado", [
(1, 200),
(10, 200),
(9999, 404),
])
def test_get_user_por_id(user_id, status_esperado):
response = requests.get(f"{BASE_URL}/users/{user_id}")
assert response.status_code == status_esperadoEse único test se ejecuta tres veces (así lo muestra pytest -v: como tres tests independientes) y cubre dos usuarios que existen y uno que no. Es la forma más directa de convertir una tabla de casos de prueba, como las que se diseñan con partición de equivalencia y valores límite, en código real sin escribir una función por fila.
Cuando alguien de nuestro equipo migra una colección de Postman a pytest, casi nunca empieza de cero: convierte cada carpeta de la colección en un archivo de test, y cada request en una función. El orden y los nombres ya existen, lo único nuevo es la sintaxis.
Las aserciones que de verdad importan
Un test que solo comprueba status_code == 200 deja pasar bugs reales: una API puede devolver 200 con un cuerpo vacío, con un campo mal escrito o con un tipo de dato incorrecto. Las aserciones que aportan valor de verdad son estas:
- Código de estado exacto, no solo si es un 2xx: un 201 (creado) y un 200 (éxito genérico) significan cosas distintas.
- Estructura del cuerpo de la respuesta: que los campos esperados existan, con
assert "email" in datao similares. - Valores concretos, no solo presencia: no basta con que exista
data["id"], comprueba que vale lo que esperas, conassert data["id"] == 1. - Casos negativos: qué devuelve la API con un ID inexistente, un campo obligatorio ausente o un token inválido. Es donde más bugs reales se esconden.
- Tiempo de respuesta cuando importa el rendimiento:
response.elapsed.total_seconds()da el tiempo real de la petición, útil para detectar endpoints que se degradan con el tiempo.
- Abrir la colección completa
- Pulsar "Send" endpoint a endpoint
- Leer cada respuesta a ojo
- Anotar qué falló y dónde
- Repetirlo en cada despliegue
pytest -v
Dónde encaja esto en tu progresión como QA
Saber automatizar no sustituye el testing exploratorio ni el pensamiento crítico de un QA manual, y tampoco es magia: un test mal diseñado con pytest da tan poca confianza como uno mal diseñado en Postman. Lo que cambia es la escala: la misma suite que hoy tarda una tarde en ejecutarse a mano, corre en segundos y sin intervención humana.
Esa diferencia es también salarial. Según los datos que recopilamos en cuánto cobra un QA en España, los perfiles con automatización suelen situarse por encima de los puramente manuales en la misma franja de experiencia. pytest, junto a Playwright o Cypress, es una de las piezas que hace ese salto posible.
Errores comunes al empezar con pytest
- Asserts demasiado débiles. Comprobar solo que la petición "no falla" (por ejemplo,
assert response is not None) no verifica nada útil sobre el comportamiento real de la API. - No usar fixtures y duplicar el setup. Repetir la misma URL, el mismo token o la misma sesión en cada función hace que un cambio pequeño obligue a tocar todo el archivo.
- Hardcodear datos que cambian. IDs, fechas o tokens de un entorno concreto rompen los tests en cuanto se ejecutan contra otro entorno o en CI/CD.
- Testear solo el camino feliz. Sin parametrize ni casos de error, la suite da una falsa sensación de cobertura.
- Un archivo gigante con todo dentro. Igual que en cualquier proyecto de código, separar los tests por recurso o funcionalidad (usuarios, pedidos, autenticación) hace que la suite sea mantenible seis meses después.
Preguntas frecuentes
¿Necesito saber programar para usar pytest en testing de APIs?
Necesitas nociones básicas de Python: variables, funciones, listas y diccionarios. No hace falta ser desarrollador ni saber construir una aplicación completa, con Python a nivel de script es suficiente para escribir tests de API con pytest.
¿Qué diferencia real hay entre pytest y Postman para testing de APIs?
Postman es una herramienta visual para explorar y probar endpoints a mano o con scripts dentro de la propia app. pytest es código real: vive en el repositorio del proyecto, se versiona con Git y corre en un pipeline de CI/CD sin depender de una interfaz gráfica.
¿Puedo usar pytest y Postman al mismo tiempo?
Sí, y es lo habitual en equipos reales. Postman para explorar una API nueva o probar algo puntual a mano, pytest para la suite de regresión que corre automáticamente en cada despliegue.
¿pytest sirve también para testear interfaces web, no solo APIs?
Sí. Combinado con paquetes como pytest-playwright, pytest también controla un navegador real. Esta guía se centra en testing de API porque es el punto de entrada más directo si vienes de Postman.
¿pytest funciona igual en un pipeline de CI/CD?
Sí. pytest es un comando de terminal, no necesita interfaz gráfica, así que corre igual en local, en GitHub Actions, en GitLab CI o en Jenkins. Es una de las razones por las que reemplaza a la ejecución manual en Postman a medida que un proyecto crece.
¿Quieres construir suites de automatización profesionales?
El curso de automatización avanzada de 17lab tiene una pista completa de pytest + Playwright, con fixtures, Page Object Model y CI/CD sobre un proyecto real.