14. Activar branch coverage y analizar decisiones parcialmente cubiertas

14.1 Objetivo del tema

En el tema anterior vimos que una línea cubierta no siempre significa que todos sus caminos fueron probados. Ahora vamos a activar la cobertura de ramas para que coverage.py detecte esas decisiones parcialmente cubiertas.

La cobertura de ramas ayuda a encontrar caminos faltantes en if, else, elif, ciclos, retornos tempranos y condiciones compuestas.

Objetivo práctico: ejecutar cobertura con --branch y leer columnas como Branch y BrPart.

14.2 Crear un módulo para analizar ramas

Crea el archivo src/tienda/riesgo.py:

def evaluar_riesgo(total, intentos_fallidos, cliente_vip=False):
    if total <= 0:
        raise ValueError("El total debe ser mayor que cero")

    if cliente_vip:
        return "bajo"

    if intentos_fallidos >= 3:
        return "alto"

    if total >= 100000:
        return "medio"

    return "bajo"

La función tiene varias decisiones. Algunas pruebas pueden cubrir líneas sin recorrer todos los caminos.

14.3 Pruebas iniciales

Crea tests/test_riesgo.py con estas pruebas:

import pytest

from tienda.riesgo import evaluar_riesgo


def test_evaluar_riesgo_cliente_vip():
    assert evaluar_riesgo(50000, 0, cliente_vip=True) == "bajo"


def test_evaluar_riesgo_intentos_fallidos():
    assert evaluar_riesgo(50000, 3) == "alto"


def test_evaluar_riesgo_rechaza_total_cero():
    with pytest.raises(ValueError):
        evaluar_riesgo(0, 0)

Estas pruebas son útiles, pero todavía falta el camino de total alto para cliente no VIP y el camino final de riesgo bajo.

14.4 Activar branch coverage con coverage.py

Con coverage.py, agrega la opción --branch al ejecutar la medición.

En Windows PowerShell:

$env:PYTHONPATH="src"
python -m coverage run --branch -m pytest
python -m coverage report -m

En Linux o macOS:

PYTHONPATH=src python -m coverage run --branch -m pytest
python -m coverage report -m

El reporte agrega información de ramas:

Name                  Stmts   Miss Branch BrPart  Cover   Missing
-----------------------------------------------------------------
src\tienda\riesgo.py     11      2      8      1    79%   11, 13

14.5 Activar branch coverage con pytest-cov

Si usas pytest-cov, agrega --cov-branch:

En Windows PowerShell:

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

En Linux o macOS:

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

El resultado es equivalente: pytest ejecuta las pruebas y coverage mide también las ramas.

14.6 Leer la columna Branch

Branch indica la cantidad de ramas detectadas en el archivo. No son líneas, sino caminos de decisión.

Por ejemplo, cada if puede aportar caminos de salida: condición verdadera y condición falsa. Coverage cuenta esas posibilidades para evaluar si fueron recorridas.

Un archivo puede tener buena cobertura de sentencias y menor cobertura de ramas si ejecutó las líneas pero dejó caminos sin recorrer.

14.7 Leer la columna BrPart

BrPart significa branches partial, es decir, ramas parcialmente cubiertas.

Una rama parcial aparece cuando coverage vio una decisión, pero no todos sus caminos fueron ejecutados. Por ejemplo:

if total >= 100000:
    return "medio"

return "bajo"

Si solo probamos totales menores a 100000, el if se evalúa, pero nunca toma el camino verdadero.

14.8 Corregir ramas faltantes

Para cubrir los caminos pendientes de riesgo.py, agregamos pruebas para total alto y para riesgo bajo final:

def test_evaluar_riesgo_total_alto():
    assert evaluar_riesgo(100000, 0) == "medio"


def test_evaluar_riesgo_bajo_sin_condiciones_especiales():
    assert evaluar_riesgo(50000, 0) == "bajo"

Estas pruebas no solo reducen BrPart. También documentan dos reglas de negocio que no estaban verificadas.

14.9 Medir nuevamente

Vuelve a ejecutar la cobertura de ramas:

En Windows PowerShell:

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

En Linux o macOS:

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

El valor de BrPart debería bajar o desaparecer para el archivo analizado.

14.10 Ver ramas en el reporte HTML

El reporte HTML es especialmente útil para branch coverage:

En Windows PowerShell:

$env:PYTHONPATH="src"
python -m pytest --cov=src --cov-branch --cov-report=html

En Linux o macOS:

PYTHONPATH=src python -m pytest --cov=src --cov-branch --cov-report=html

Luego abre htmlcov/index.html. En los archivos con ramas parciales, el reporte visual ayuda a ubicar qué decisión quedó incompleta.

14.11 No toda rama parcial exige una prueba

Algunas ramas parciales pueden corresponder a código defensivo, condiciones imposibles por diseño o caminos que conviene excluir con una justificación clara.

Antes de agregar una prueba, pregúntate:

  • ¿Este camino representa un comportamiento real?
  • ¿Puede ocurrir con entradas válidas?
  • ¿La prueba aumentaría confianza o solo el número?
  • ¿Conviene simplificar el código en lugar de probarlo?

14.12 Branch coverage y condiciones compuestas

Las condiciones con and y or merecen atención especial:

def requiere_revision(total, intentos_fallidos):
    return total >= 100000 or intentos_fallidos >= 3

Casos útiles:

def test_requiere_revision_por_total():
    assert requiere_revision(100000, 0) is True


def test_requiere_revision_por_intentos():
    assert requiere_revision(1000, 3) is True


def test_no_requiere_revision():
    assert requiere_revision(1000, 0) is False

Estas pruebas cubren las razones principales por las que la función puede devolver True y el camino donde devuelve False.

14.13 Errores frecuentes

  • Activar branch coverage sin revisar BrPart: la columna más útil suele ser la de ramas parciales.
  • Confundir Miss con ramas faltantes: una línea puede estar cubierta y aun así tener una rama sin probar.
  • Agregar pruebas mecánicas: cada rama cubierta debe representar un comportamiento relevante.
  • No regenerar el reporte HTML: después de cambiar pruebas, vuelve a ejecutar la medición.

14.14 Conclusión

En este tema activamos cobertura de ramas con --branch y --cov-branch. También interpretamos Branch, BrPart y los reportes de ramas parciales.

En el próximo tema vamos a usar parametrización para aumentar cobertura sin duplicar código.