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.
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.
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.
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.
Antes de escribir pruebas, conviene leer qué representa cada línea faltante.
El reporte no reemplaza el criterio. Solo indica qué caminos quedaron sin ejecutar; nosotros decidimos qué comportamiento debe verificarse.
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.
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.
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.
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.
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
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.
pytest.mark.parametrize.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.