9. Cobertura de funciones puras: agregar pruebas donde el reporte lo indica

9.1 Objetivo del tema

Las funciones puras son un buen punto de partida para mejorar cobertura: reciben datos, calculan un resultado y no dependen de archivos, red, base de datos ni estado externo.

En este tema vamos a usar el reporte de cobertura para encontrar caminos no probados y agregar pruebas que verifiquen comportamientos concretos.

Objetivo práctico: leer líneas faltantes en una función pura y convertirlas en pruebas unitarias útiles.

9.2 Agregar un módulo de ejemplo

Crea el archivo src/tienda/envios.py con estas funciones:

def calcular_costo_envio(total_compra, distancia_km):
    if total_compra < 0:
        raise ValueError("El total de compra no puede ser negativo")

    if distancia_km < 0:
        raise ValueError("La distancia no puede ser negativa")

    if total_compra >= 50000:
        return 0

    if distancia_km <= 5:
        return 1500

    if distancia_km <= 20:
        return 3000

    return 5000


def clasificar_envio(costo):
    if costo == 0:
        return "gratis"

    if costo <= 2000:
        return "economico"

    return "estandar"

Ambas funciones son puras: para los mismos argumentos, devuelven siempre el mismo resultado o lanzan la misma excepción.

9.3 Escribir pruebas iniciales

Crea el archivo tests/test_envios.py con una primera versión de pruebas:

from tienda.envios import calcular_costo_envio, clasificar_envio


def test_calcular_costo_envio_gratis_por_total_alto():
    assert calcular_costo_envio(50000, 10) == 0


def test_calcular_costo_envio_cercano():
    assert calcular_costo_envio(10000, 5) == 1500


def test_clasificar_envio_gratis():
    assert clasificar_envio(0) == "gratis"

Estas pruebas cubren algunos caminos, pero no todos. El reporte nos va a mostrar qué falta recorrer.

9.4 Medir cobertura

Ejecuta pytest con cobertura y líneas faltantes.

En Windows PowerShell:

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

En Linux o macOS:

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

Una salida posible para el nuevo archivo sería:

Name                   Stmts   Miss  Cover   Missing
----------------------------------------------------
src\tienda\envios.py      18      7    61%   3, 6, 15, 17, 24, 26, 28

Los números exactos pueden variar si modificaste el código, pero la idea es la misma: el reporte señala caminos que las pruebas todavía no ejecutan.

9.5 Interpretar las líneas faltantes

Antes de escribir pruebas, conviene leer qué representa cada línea faltante.

  • Validaciones: compras negativas y distancias negativas.
  • Tramos de distancia: envío intermedio y envío lejano.
  • Clasificación: costo económico y costo estándar.

El reporte no reemplaza el criterio. Solo indica qué caminos quedaron sin ejecutar; nosotros decidimos qué comportamiento debe verificarse.

9.6 Agregar pruebas para validaciones

Primero cubrimos los caminos de error, porque son reglas importantes de la función:

import pytest

from tienda.envios import calcular_costo_envio, clasificar_envio


def test_calcular_costo_envio_rechaza_total_negativo():
    with pytest.raises(ValueError):
        calcular_costo_envio(-1, 10)


def test_calcular_costo_envio_rechaza_distancia_negativa():
    with pytest.raises(ValueError):
        calcular_costo_envio(10000, -1)

Estas pruebas no solo suben la cobertura: verifican que la función rechace datos inválidos.

9.7 Agregar pruebas para caminos de negocio

Ahora agregamos pruebas para los distintos tramos de distancia:

def test_calcular_costo_envio_distancia_intermedia():
    assert calcular_costo_envio(10000, 20) == 3000


def test_calcular_costo_envio_distancia_lejana():
    assert calcular_costo_envio(10000, 21) == 5000

Estas pruebas describen reglas del negocio: cerca, intermedio y lejos tienen costos diferentes.

9.8 Agregar pruebas para clasificación

También faltaban caminos en clasificar_envio:

def test_clasificar_envio_economico():
    assert clasificar_envio(1500) == "economico"


def test_clasificar_envio_estandar():
    assert clasificar_envio(3000) == "estandar"

Con estas pruebas verificamos todos los resultados posibles de la función.

9.9 Medir nuevamente

Vuelve a ejecutar la cobertura:

En Windows PowerShell:

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

En Linux o macOS:

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

El reporte de envios.py debería mejorar y la lista de líneas faltantes debería reducirse o desaparecer.

9.10 No cubrir por cubrir

Una mala reacción ante el reporte sería escribir pruebas que solo ejecuten líneas sin verificar nada importante. Por ejemplo, una prueba con aserciones débiles puede aumentar el porcentaje sin mejorar la confianza.

def test_envio_debil():
    resultado = calcular_costo_envio(10000, 30)
    assert resultado is not None

La prueba ejecuta código, pero no verifica la regla real. Es mejor afirmar el resultado esperado:

def test_calcular_costo_envio_distancia_lejana():
    assert calcular_costo_envio(10000, 30) == 5000

9.11 Usar parametrización

Cuando una función pura tiene varios casos similares, la parametrización evita duplicación:

import pytest


@pytest.mark.parametrize(
    "total, distancia, esperado",
    [
        (50000, 10, 0),
        (10000, 5, 1500),
        (10000, 20, 3000),
        (10000, 21, 5000),
    ],
)
def test_calcular_costo_envio(total, distancia, esperado):
    assert calcular_costo_envio(total, distancia) == esperado

Este enfoque mantiene las pruebas compactas sin ocultar los casos importantes.

9.12 Problemas frecuentes

  • La cobertura sube pero las pruebas no aportan confianza: revisa que cada prueba tenga una aserción significativa.
  • Muchas pruebas se parecen demasiado: considera usar pytest.mark.parametrize.
  • No sabes qué probar de una línea faltante: identifica qué regla o caso de uso representa esa línea.
  • El reporte muestra código que no debería existir: evalúa eliminarlo antes de escribir pruebas artificiales.

9.13 Conclusión

En este tema usamos el reporte de cobertura para mejorar pruebas de funciones puras. Pasamos de líneas faltantes a comportamientos concretos: validaciones, casos borde y resultados esperados.

En el próximo tema trabajaremos con validaciones, excepciones y casos borde de manera más específica.