Saltar a contenido

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: TestFeatureName para 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