This repository documents my learning journey with pytest, automation, and testing best practices using Python.
This lab includes:
- Test-driven examples using
pytest - Parametrization and fixtures
- Exception handling and advanced test scenarios
- Code profiling and optimization techniques
pytest-uv-project/
├── src/ # Core code
├── tests/ # Unit and integration tests
└── pyproject.toml # uv project configuration
# Activar el entorno virtual
uv venv
# Instalar pytest y plugins
uv pip install pytest pytest-cov pytest-mockuv run pytest -v
uv run pytest -m error_handling -v
✔️ Week 1: Pytest Fundamentals
🚧 Week 2: Mocking & Fixtures
🔜 Week 3: CI/CD & Coverage Reports
Esta sección documenta los fundamentos clave de pytest aplicados en este proyecto, combinando teoría y práctica.
El objetivo es reforzar conceptos esenciales para crear pruebas más limpias, escalables y mantenibles en Python.
Una fixture es una función especial que prepara datos o recursos antes de ejecutar una prueba.
Ayuda a eliminar código repetido y asegura que cada test empiece con un entorno limpio y controlado.
import pytest
@pytest.fixture
def usuario_base():
return {"nombre": "Luis", "rol": "admin"}
def test_usuario_tiene_rol(usuario_base):
assert usuario_base["rol"] == "admin"💡 Nota: Las fixtures ayudan a mantener las pruebas aisladas y predecibles.
✅ Buenas prácticas
- Usa nombres descriptivos para tus fixtures.
- Evita modificar los datos compartidos dentro del test.
- Reutiliza fixtures solo cuando su configuración sea estable.
tmp_path es una fixture integrada en pytest que crea carpetas temporales únicas por test. Es ideal para pruebas que requieren lectura o escritura de archivos sin ensuciar el proyecto.
import pytest
def test_generar_reporte(tmp_path):
archivo = tmp_path / "reporte.txt"
archivo.write_text("resultado exitoso")
contenido = archivo.read_text()
assert "exitoso" in contenidoDentro de una fixture, yield define el punto de entrega del recurso al test. El código después del yield se ejecuta siempre al terminar la prueba, incluso si falla.
@pytest.fixture
def conexion_db():
conn = conectar_db()
yield conn
conn.close() # Se ejecuta al final del test, pase o falle🧠 A diferencia de unittest, que usa setup() y teardown(), en pytest el control se logra con yield, simplificando la sintaxis.
| Concepto | Qué hace | Cuándo usarlo |
|---|---|---|
| Fixture | Prepara datos o recursos | Cuando hay código repetido |
| tmp_path | Crea archivos temporales | Pruebas con lectura/escritura |
| yield | Controla inicio y limpieza | Cuando se usan recursos externos |
📅 Progreso del aprendizaje — Día 3
Hoy avancé con las fixtures en pytest.
Aprendí a usar tmp_path, yield y scope, entendiendo cómo ayudan a mantener los tests limpios y seguros.
Cada día me resulta más claro cómo pytest puede hacer el proceso de testing más elegante y profesional.
Hoy exploré cómo pytest permite ejecutar la misma prueba con múltiples combinaciones de datos sin repetir código, utilizando parametrización y fixtures parametrizadas.
Este enfoque mejora la cobertura y mantiene las pruebas limpias y reutilizables.
Permite definir un conjunto de datos para ejecutar el mismo test múltiples veces.
import pytest
from src.calculator.math_ops import MathOperations
@pytest.mark.parametrize("a,b,resultado", [
(2, 3, 5),
(4, 1, 3),
(10, -5, 5)
])
def test_add(a, b, resultado):
assert MathOperations.add(a, b) == resultado📘 Reflexión del día Hoy entendí que pytest no solo sirve para validar resultados, sino también para diseñar tests más expresivos y reutilizables. Con cada refactor, el testing se siente menos como una tarea extra y más como una herramienta de calidad y aprendizaje continuo.
En este día aprendimos a combinar fixtures, parametrización y markers personalizados,
tres pilares para estructurar pruebas profesionales y reutilizables en Python.
Además, exploramos la ejecución selectiva y el manejo de casos de error.
[tool.pytest.ini_options]
markers = [
"rapidas: pruebas rápidas",
"lentas: pruebas con operaciones costosas",
"error_handling: validación de manejo de errores"
]| Concepto | Qué hace | Cuándo usarlo |
|---|---|---|
| @pytest.mark.parametrize | Ejecuta múltiples escenarios con un mismo test | Varias combinaciones de entrada |
| Fixtures combinadas | Reutiliza datos o configuraciones | Escenarios repetitivos |
| Markers personalizados | Agrupa y filtra pruebas | Ejecución selectiva (rápidas, lentas, errores) |
| conftest.py | Centraliza fixtures globales | Configuración común de tests |
Aprender a aislar el código que depende de servicios externos (APIs, correo, archivos, BD, etc.) utilizando herramientas de mocking que ofrece pytest + unittest.mock. “El mocking no es mentirle al código — es enseñarle a pensar por sí mismo en un entorno controlado, es construir un entorno seguro donde el código puede fallar sin consecuencias.”
Antes de ejecutar los ejemplos, instala la librería requests en tu entorno virtual manejado por uv:
# Activar entorno virtual si no está activo
uv venv
# Instalar requests dentro del entorno
uv pip install requests| Elemento | Qué hace | Ejemplo |
|---|---|---|
| @patch("src.calculator.financial.requests.get") | Sustituye la función real requests.get por un mock temporal | Evita llamadas reales a internet |
| MagicMock() | Crea un objeto flexible que puede simular cualquier atributo o método | mock_response.json.return_value = {...} |
| assert_called_once_with() | Verifica que la función se haya llamado correctamente | Previene llamadas duplicadas o erróneas |
| pytest.raises(ConnectionError) | Comprueba el flujo de error esperado | Asegura que la app responde correctamente a fallos externos |
| Concepto | Descripción | Beneficio |
|---|---|---|
| Mock de requests | Sustituye llamadas reales a la red | Tests sin conexión |
| MagicMock | Crea objetos con comportamiento controlado | Simula APIs o archivos |
| Parametrización con mocks | Prueba múltiples escenarios rápidamente | Mayor cobertura |
| Combinación con markers | Clasifica tests por tipo (API, errores, etc.) | Organización profesional |
💡 Esta guía resume los comandos y patrones más útiles de pytest cuando se combina con uv.
Ideal como referencia rápida durante el desarrollo o debugging de tests.
| Objetivo | Comando |
|---|---|
| Ejecutar todos los tests | uv run pytest |
| Ejecutar un archivo | uv run pytest tests/test_math_ops.py |
| Ejecutar una función específica | uv run pytest tests/test_math_ops.py::test_sumatoria_basica |
| Ejecutar por marker | uv run pytest -m api |
| Ejecutar con varios markers | uv run pytest -m "api or error_handling" |
| Ejecutar con filtro de nombre | uv run pytest -k interest |
🎯 Objetivo: aprender a simular comportamientos externos (APIs, archivos, bases de datos)
sin depender de servicios reales, usando mocking para mantener las pruebas rápidas, seguras y determinísticas.
El mocking (simulación) permite reemplazar dependencias externas por objetos falsos
que imitan su comportamiento esperado durante las pruebas.
Se usa para probar la lógica del código sin depender de factores externos como:
- 🔗 APIs externas que pueden fallar o ser lentas
- 💾 Archivos o bases de datos con acceso restringido
- 🌐 Conexiones HTTP no disponibles en el entorno de test
Ya que trabajamos con uv, instalaremos pytest-mock y requests así:
uv add pytest-mock requestsEsto permite usar la fixture mocker, incluida con pytest-mock, para simular objetos o funciones.
🧠 Conclusión del Día 7
| Concepto | Qué Aprendiste |
|---|---|
| mocker.patch() | Reemplazar funciones o módulos externos |
| return_value | Simular valores de retorno |
| assert_called_with() | Verificar argumentos usados en la llamada |
| Parametrización + Mock | Probar varios escenarios de API en una sola función |
| Fixture de Mock | Reutilizar simulaciones en múltiples tests |
🧩 Aprender a hacer mocking no solo ahorra tiempo, sino que también permite testear lo imposible: lo que aún no existe o no se puede conectar.
Objetivo: profundizar en el uso del mocking para manejar respuestas dinámicas, excepciones y efectos colaterales.
- Uso de
side_effectpara simular errores y respuestas múltiples. - Pruebas con
pytest.raisespara validar excepciones. - Creación de fixtures reutilizables de mocks.
- Validación de llamadas al sistema (
time.sleep) sin ejecutar código real.
src/mailing/email_client.pytests/test_email_client_advanced.py
side_effectpermite que los mocks tengan comportamientos dinámicos.- Las fixtures de mocks ayudan a mantener los tests limpios y consistentes.
- Podemos probar tiempos, errores y reintentos sin depender de servicios reales.
“Un buen test no sólo verifica el éxito, también asegura que el sistema falla correctamente.”
Issue:
El cliente de correo no realizaba reintentos cuando la API devolvía errores HTTP (p. ej. 500). Solo reintentaba ante excepciones.
Objetivos:
- Implementar reintentos también en caso de fallos HTTP.
- Simular respuestas HTTP con
MagicMockjunto con distintos tipos de fallos HTTP y excepciones. - Implementar
side_effectmixtos (fallos de red + errores 500 + éxito). - Validar que los reintentos ocurren sin hacer llamadas reales a internet.
- Aprender a usar
requests.Responsereal dentro de mocks. - Verificar correctamente la cantidad de reintentos.
Conceptos clave:
mock_post.side_effectpermite controlar el flujo completo de ejecución simulada.- El método
enviar_con_reintentoahora distingue entre respuestas exitosas (200) y errores. time.sleepsigue siendo parcheado para medir la cantidad de reintentos sin demoras reales.- El éxito anticipado reduce el número real de llamadas a
sleep()ypost(). - El mock debe aplicarse donde se usa, no donde se define.
- Puedes anidar mocks para probar desde el nivel de API hasta la capa HTTP.
Resultado esperado: Todos los tests deben pasar, incluyendo los escenarios con:
- Éxitos inmediatos.
- Fallos intermitentes.
- Errores HTTP simulados.
- Escenarios complejos de error → retry → éxito
Objetivo:
Aprender a usar fixtures de pytest para preparar entornos y objetos compartidos en múltiples tests.
Conceptos clave:
- Las fixtures son funciones que inicializan datos o mocks comunes.
- Se pueden inyectar automáticamente mediante su nombre como parámetro.
conftest.pyes el punto central donde se definen las fixtures globales.make_responsegenera respuestas HTTP realistas.mock_postevita llamadas externas, garantizando aislamiento.
Resultado esperado:
- Tests más cortos, expresivos y reutilizables.
- Cero código duplicado entre tests.
- Facilita mantenimiento y escalabilidad de la suite de pruebas.