Curso de Python / Automatizar pruebas unitarias y de integración con unittest

Las pruebas automatizadas comprueban de forma repetible resultados o excepciones para entradas definidas. En Python, unittest permite convertir esas expectativas en fallos detectables para descubrir regresiones cuando cambia la implementación.

Definir reglas observables antes de probar

La función del ejemplo recibe una lista de enteros no negativos, suma sus valores y devuelve cero si la lista está vacía. Rechaza booleanos, cadenas y números negativos. Al empezar con un cálculo sin reloj, red ni archivos personales, las condiciones de la prueba son fáciles de reproducir.

Una prueba útil expresa una regla requerida por el usuario, no una copia de las líneas de la implementación. Ver un número impreso no ofrece la misma comprobación automática que compararlo con un resultado esperado mediante una aserción.

Guardar la función y sus pruebas completas

En una carpeta nueva guarda expense_math.py:

def total(amounts):
    if any(type(x) is not int or x < 0 for x in amounts):
        raise ValueError("invalid amount")
    return sum(amounts)

Junto a él, guarda test_expense_math.py. El prefijo test_ ayuda al descubrimiento de archivos y métodos de prueba.

import unittest
from expense_math import total

class TotalTests(unittest.TestCase):
    def test_regular(self):
        self.assertEqual(total([3500, 8000, 2500]), 14000)

    def test_empty(self):
        self.assertEqual(total([]), 0)

    def test_invalid(self):
        for values in [[-1], [True], ["8000"]]:
            with self.subTest(values=values):
                with self.assertRaises(ValueError):
                    total(values)

if __name__ == "__main__":
    unittest.main()

Desde esa carpeta ejecuta:

python -m unittest -v

El resultado debe indicar tres pruebas y terminar en OK. subTest distingue los tres casos inválidos dentro de una prueba. La duración y el formato exacto pueden variar; comprueba el estado y el significado del resultado.

Confirmar que una implementación defectuosa falla

  1. Cambia temporalmente el retorno a sum(amounts) + 1. Las pruebas de entrada normal y lista vacía deben fallar.
  2. Restaura el cálculo y repite hasta obtener OK.
  3. Retira temporalmente la validación, observa los fallos de los casos inválidos y vuelve a incorporarla.

Estas modificaciones deliberadas comprueban que las pruebas protegen las reglas previstas. No dejes la versión incorrecta como resultado final. La documentación de unittest describe las aserciones y la organización de pruebas.

Probar archivos y comandos como una integración

Que una suma funcione no demuestra que el encabezado CSV, la escritura o el código de salida sean correctos. Para conectar esas piezas, coloca el programa completo csv_report.py del proyecto de informes CSV en esta carpeta. Guarda el siguiente código Python como check_csv.py y ejecuta python check_csv.py.

import subprocess
import sys
import tempfile
from pathlib import Path
program = Path("csv_report.py").resolve()
with tempfile.TemporaryDirectory() as folder:
    work = Path(folder)
    source = work / "expenses-input.csv"
    source.write_text("id,category,amount\ne1,food,8000\ne1,food,8000\n", encoding="utf-8")
    result = subprocess.run([sys.executable, str(program)], cwd=work, capture_output=True, text=True)
    assert result.returncode == 1
    assert "food,8000" in (work / "report.csv").read_text(encoding="utf-8")
    assert "duplicate id" in (work / "rejected.csv").read_text(encoding="utf-8")
print("CSV integration checks passed")
CSV integration checks passed

La prueba crea una entrada con un identificador duplicado en una carpeta temporal. Comprueba el código de salida uno, el importe aceptado y la razón de rechazo, sin sobrescribir informes del proyecto. El directorio temporal se elimina al salir del contexto.

Las pruebas unitarias aíslan reglas pequeñas; las de integración comprueban las conexiones entre componentes. El gestor final de gastos utiliza el mismo principio con una base de datos temporal. Al documentar resultados, conserva la versión del intérprete, los comandos y los casos realmente ejecutados: superar estas pruebas no demuestra otros entornos o datos que no se hayan comprobado.