Testing¶
Comandos¶
pytest -x # Stop on first failure
pytest -x -q # Quiet mode
pytest -m "not slow" # Excluir tests lentos
pytest --cov=src --cov-report=term # Con coverage
pytest tests/unit/ # Solo unit tests
pytest tests/e2e/ # Solo e2e tests
pytest -k "test_create" # Filtrar por nombre
Markers¶
@pytest.mark.slow # Tests que toman >5s (OCR, ML, etc.)
@pytest.mark.integration # Requieren infra externa
@pytest.mark.e2e # End-to-end (browser, API)
Estructura¶
tests/
unit/ # Logica aislada, sin I/O
test_entities.py
test_use_cases.py
test_property_based.py # Hypothesis PBT
integration/ # Con BD, filesystem
test_repository.py
e2e/ # Flujos completos
test_flows.py
fixtures/ # Datos de prueba compartidos
sample_data.py
conftest.py # Fixtures globales
Convenciones¶
- Nombre:
test_<que_hace>_<condicion>->test_create_user_with_invalid_email_fails - Clase:
TestFeatureNamepara agrupar tests relacionados - Fixture:
@dataclass(frozen=True)para datos inmutables - Aislamiento: Cada test independiente, sin estado compartido
- Imports: Absolutos desde el paquete (
from mi_proyecto.core import ...)
Property-Based Testing (Hypothesis)¶
Para funciones puras con dominio amplio de inputs:
from hypothesis import given, strategies as st
@given(st.text(max_size=100))
def test_sanitize_never_returns_invalid(raw: str) -> None:
result = sanitize(raw)
assert is_valid(result)
Patrones utiles:
- Idempotencia: f(f(x)) == f(x)
- Invariante: output siempre cumple una propiedad
- Roundtrip: decode(encode(x)) == x
- Oracle: simple(x) == optimized(x)
Mocking¶
Usar sparingly. Preferir inyeccion de dependencias:
# BIEN — inyeccion
def test_process_document(fake_repo: FakeRepository):
use_case = ProcessUseCase(repo=fake_repo)
result = use_case.execute(data)
assert result.is_success
# EVITAR — mock excesivo
@patch("module.Class.method")
@patch("module.other_function")
def test_something(mock1, mock2):
... # Fragil, acoplado a implementacion