23. Estrategia práctica para mejorar cobertura sin escribir pruebas frágiles

23.1 Objetivo del tema

Después de aprender comandos, reportes, configuración y cobertura de ramas, falta una pregunta práctica: cómo mejorar cobertura de manera ordenada sin llenar el proyecto de pruebas frágiles.

En este tema vamos a definir una estrategia de trabajo para decidir dónde probar, qué probar y cuándo conviene refactorizar antes de escribir más pruebas.

Objetivo práctico: convertir un reporte de cobertura en un plan de mejora con pruebas útiles y mantenibles.

23.2 Qué es una prueba frágil

Una prueba frágil falla ante cambios que no rompen el comportamiento importante del sistema. Normalmente está demasiado atada a detalles internos.

Señales comunes:

  • Verifica detalles de implementación que no importan al usuario del código.
  • Depende del orden interno de operaciones sin necesidad.
  • Repite demasiada lógica del código probado.
  • Necesita cambios frecuentes aunque el comportamiento siga igual.

23.3 Paso 1: medir desde cero

Antes de decidir, genera un reporte limpio.

En Windows PowerShell:

$env:PYTHONPATH="src"
python -m coverage erase
python -m pytest --cov=src --cov-branch --cov-report=term-missing

En Linux o macOS:

python -m coverage erase
PYTHONPATH=src python -m pytest --cov=src --cov-branch --cov-report=term-missing

Trabaja con datos actuales. No tomes decisiones sobre reportes viejos.

23.4 Paso 2: priorizar módulos

No empieces por cualquier línea faltante. Primero identifica módulos importantes con cobertura baja o ramas parciales.

Name                      Stmts   Miss Branch BrPart  Cover   Missing
---------------------------------------------------------------------
src\tienda\pagos.py          40     16     14      5    59%   12-18, 26, 31-38
src\tienda\carrito.py        25      2      8      1    88%   27, 35
src\tienda\tarifas.py        17      0     12      0   100%

Si pagos.py es crítico, probablemente convenga empezar ahí aunque no sea el único archivo con líneas faltantes.

23.5 Paso 3: leer el código antes de probar

Abre el archivo señalado por el reporte y entiende qué comportamiento representa cada línea faltante.

Clasifica las líneas faltantes:

  • Regla de negocio: conviene probarla.
  • Validación o error esperado: conviene probarlo.
  • Caso borde: conviene probarlo.
  • Código muerto: conviene eliminarlo o revisarlo.
  • Detalle de infraestructura: quizá convenga excluirlo o probarlo en otro nivel.

23.6 Paso 4: escribir pruebas por comportamiento

Evita pruebas que solo existen para ejecutar una línea. Es mejor nombrar el comportamiento:

def test_autorizar_pago_tarjeta_monto_alto_requiere_revision():
    assert autorizar_pago(150000, "tarjeta", 0) == "revision"

El nombre de la prueba explica la regla. Si esa regla se rompe, la falla será fácil de interpretar.

23.7 Paso 5: evitar aserciones débiles

Una prueba como esta sube cobertura, pero aporta poca confianza:

def test_pago_debil():
    resultado = autorizar_pago(150000, "tarjeta", 0)
    assert resultado is not None

Mejor verifica el resultado esperado:

def test_pago_monto_alto_va_a_revision():
    assert autorizar_pago(150000, "tarjeta", 0) == "revision"

23.8 Paso 6: cubrir bordes, no combinaciones infinitas

No hace falta probar todos los valores posibles. Elige casos representativos:

  • Justo antes del límite.
  • Justo en el límite.
  • Justo después del límite.
  • Entradas inválidas relevantes.
  • Caminos alternativos de negocio.

La parametrización ayuda cuando esos casos comparten estructura.

23.9 Paso 7: refactorizar si la prueba sale difícil

Si una función es muy difícil de probar, puede ser una señal de diseño. Tal vez mezcla cálculo, entrada de usuario, red, archivos o impresión por pantalla.

Antes:

def procesar_compra():
    total = float(input("Total: "))
    descuento = total * 0.10
    print(f"Total final: {total - descuento}")

Después:

def calcular_total_final(total):
    return total - total * 0.10


def procesar_compra():
    total = float(input("Total: "))
    print(f"Total final: {calcular_total_final(total)}")

Ahora la lógica importante puede probarse como función pura.

23.10 Paso 8: medir de nuevo

Después de agregar pruebas o refactorizar, vuelve a ejecutar:

python -m pytest --cov=src --cov-branch --cov-report=term-missing

Revisa dos cosas:

  • Si bajaron las líneas faltantes o ramas parciales esperadas.
  • Si las pruebas nuevas fallarían ante un error real.

23.11 Checklist de una buena prueba

  • Nombre claro: describe comportamiento esperado.
  • Preparación mínima: crea solo los datos necesarios.
  • Aserción concreta: verifica resultado, excepción o estado relevante.
  • Independencia: no depende de otras pruebas.
  • Mantenibilidad: no se ata a detalles internos innecesarios.

23.12 Cuándo excluir o ignorar

No todo hueco de cobertura se arregla con una prueba nueva.

  • Código generado: puede excluirse por configuración.
  • Punto de entrada manual: puede usar pragma: no cover.
  • Código muerto: conviene eliminarlo.
  • Integración externa: quizá requiere pruebas de integración, no unitarias.

23.13 Plan de mejora incremental

Una estrategia realista para un proyecto existente:

  1. Configurar medición consistente.
  2. Fijar un mínimo cercano al estado actual.
  3. Priorizar un módulo crítico.
  4. Agregar pruebas de comportamiento y bordes.
  5. Subir el mínimo cuando la mejora sea estable.
  6. Repetir con otro módulo.

23.14 Errores frecuentes

  • Perseguir líneas en orden del reporte: prioriza por riesgo y comportamiento.
  • Escribir pruebas frágiles: evita depender de detalles internos innecesarios.
  • Subir cobertura con aserciones débiles: el porcentaje mejora, la confianza no.
  • No refactorizar nunca: a veces mejorar testabilidad requiere mejorar diseño.

23.15 Conclusión

En este tema organizamos una estrategia para mejorar cobertura sin degradar la calidad de las pruebas. La clave es pasar del reporte a comportamientos verificables, priorizando módulos críticos y evitando pruebas frágiles.

En el próximo tema vamos a cerrar el curso con un caso práctico integrador.