27. Limpieza automática de datos, archivos y recursos después de cada prueba

27.1 Objetivo del tema

Una prueba automatizada debe dejar el entorno en buen estado. Si una prueba crea archivos, modifica variables o prepara recursos, debe evitar afectar a las pruebas siguientes.

En este tema veremos técnicas para limpiar datos, archivos y recursos después de cada prueba usando fixtures, yield, tmp_path y herramientas de pytest.

Objetivo práctico: asegurar que cada prueba sea independiente y no deje residuos que afecten a otras ejecuciones.

27.2 Por qué importa la limpieza

Si una prueba deja residuos, otra prueba puede fallar o pasar por una razón incorrecta. Esto genera suites frágiles y difíciles de diagnosticar.

Problemas típicos:

  • Archivos temporales que quedan en la raíz del proyecto.
  • Variables de entorno modificadas.
  • Listas o diccionarios globales alterados.
  • Carpetas de reportes mezcladas con datos de prueba.
  • Recursos abiertos sin cerrar.

27.3 Preferir tmp_path para archivos temporales

Cuando una prueba necesita crear archivos, la primera opción debería ser tmp_path:

def test_crea_archivo_temporal(tmp_path):
    ruta = tmp_path / "salida.txt"
    ruta.write_text("contenido", encoding="utf-8")

    assert ruta.exists()

pytest se encarga de manejar esa carpeta temporal sin ensuciar el proyecto.

27.4 Evitar archivos compartidos

Evita escribir directamente en archivos compartidos como:

from pathlib import Path


def test_mala_practica():
    Path("salida.txt").write_text("contenido", encoding="utf-8")

Ese archivo queda en la raíz del proyecto y puede afectar otras pruebas o ejecuciones posteriores.

27.5 Limpieza con yield en fixtures

Cuando una fixture prepara un recurso que debe limpiarse, usa yield:

import pytest


@pytest.fixture
def recurso_temporal():
    recurso = {"abierto": True}

    yield recurso

    recurso["abierto"] = False

El código posterior a yield se ejecuta al finalizar la prueba.

27.6 Fixture para carpeta de trabajo

Podemos crear una carpeta temporal preparada:

import pytest


@pytest.fixture
def carpeta_trabajo(tmp_path):
    entrada = tmp_path / "entrada"
    salida = tmp_path / "salida"
    entrada.mkdir()
    salida.mkdir()

    return {
        "entrada": entrada,
        "salida": salida,
    }

Como usa tmp_path, no necesita limpieza manual.

27.7 Probar con carpeta de trabajo

Ejemplo de uso:

def test_crea_archivo_en_carpeta_salida(carpeta_trabajo):
    ruta = carpeta_trabajo["salida"] / "resultado.txt"
    ruta.write_text("ok", encoding="utf-8")

    assert ruta.read_text(encoding="utf-8") == "ok"

La prueba no escribe en carpetas reales del proyecto.

27.8 Limpieza de variables de entorno

Para variables de entorno, usa monkeypatch. pytest restaura los cambios automáticamente.

def test_configuracion_temporal(monkeypatch):
    monkeypatch.setenv("APP_ENV", "testing")

    assert obtener_ambiente() == "testing"

No necesitas limpiar manualmente APP_ENV después de la prueba.

27.9 Limpiar listas globales

Si tienes un recurso global, es mejor evitarlo. Pero si existe, usa una fixture para restaurarlo.

EVENTOS = []


def registrar_evento(evento):
    EVENTOS.append(evento)

Fixture de limpieza:

import pytest


@pytest.fixture
def eventos_limpios():
    EVENTOS.clear()
    yield EVENTOS
    EVENTOS.clear()

27.10 Probar usando estado limpio

Uso de la fixture:

def test_registrar_evento_agrega_evento(eventos_limpios):
    registrar_evento("usuario_creado")

    assert eventos_limpios == ["usuario_creado"]

La lista se limpia antes y después de la prueba.

27.11 Cerrar recursos abiertos

Si abres un archivo manualmente, ciérralo. Mejor aún, usa with:

def test_escritura_con_context_manager(tmp_path):
    ruta = tmp_path / "datos.txt"

    with ruta.open("w", encoding="utf-8") as archivo:
        archivo.write("contenido")

    assert ruta.read_text(encoding="utf-8") == "contenido"

El context manager cierra el archivo automáticamente.

27.12 Limpieza con try/finally

En código de aplicación o scripts, try/finally permite limpiar aunque ocurra un error:

def usar_recurso(recurso):
    try:
        recurso["abierto"] = True
        return "ok"
    finally:
        recurso["abierto"] = False

En pruebas, las fixtures con yield suelen ser más cómodas.

27.13 Limpieza de reportes

Los reportes son artefactos generados. Puedes limpiarlos antes de una ejecución usando el script creado en temas anteriores:

python scripts/limpiar_reportes.py

No limpies reportes si necesitas conservar evidencia de una ejecución anterior.

27.14 autouse para limpieza común

Una fixture autouse puede limpiar algo antes y después de cada prueba:

import pytest


@pytest.fixture(autouse=True)
def limpiar_eventos():
    EVENTOS.clear()
    yield
    EVENTOS.clear()

Úsala con cuidado. Si la limpieza no es evidente, puede confundir a quien lee la prueba.

27.15 Cuándo evitar autouse

Evita autouse=True cuando la limpieza afecta solo a algunas pruebas. En ese caso, es más claro pedir la fixture explícitamente:

def test_registrar_evento_agrega_evento(eventos_limpios):
    registrar_evento("usuario_creado")

    assert eventos_limpios == ["usuario_creado"]

27.16 Limpieza y ejecución paralela

La limpieza es todavía más importante cuando ejecutamos pruebas en paralelo. Si dos pruebas escriben el mismo archivo, pueden fallar de forma intermitente.

Buenas prácticas para paralelo:

  • Usar tmp_path.
  • No usar nombres de archivos compartidos.
  • No depender de estado global.
  • Preparar datos dentro de cada prueba o fixture.

27.17 Verificar que no quedan residuos

Puedes crear una prueba o script que verifique que no hay archivos inesperados en la raíz. Por ejemplo:

from pathlib import Path


def test_no_existe_salida_txt_en_raiz():
    assert not Path("salida.txt").exists()

Este tipo de prueba puede ayudar a detectar prácticas incorrectas mientras se aprende.

27.18 Qué no debe limpiarse desde una prueba

Una prueba no debería borrar carpetas del proyecto, código fuente, archivos de configuración ni datos versionados.

La limpieza debe limitarse a recursos creados por la propia prueba o a carpetas claramente generadas, como reports cuando se ejecuta un script de limpieza.

27.19 Problemas frecuentes

  • Una prueba pasa sola y falla con toda la suite: revisa residuos o estado compartido.
  • Quedan archivos en la raíz: usa tmp_path.
  • Una variable de entorno afecta otras pruebas: usa monkeypatch.
  • Una fixture autouse causa confusión: haz la fixture explícita.
  • Fallas solo en paralelo: busca recursos compartidos sin aislamiento.

27.20 Ejercicio práctico

Crea app/eventos.py con una lista global EVENTOS y una función registrar_evento. Luego crea una fixture que limpie la lista antes y después de cada prueba que la use.

Automatiza estas pruebas:

  • Registrar un evento agrega un elemento.
  • Registrar dos eventos conserva el orden.
  • Una prueba nueva comienza con la lista vacía.

27.21 Solución propuesta

Archivo app/eventos.py:

EVENTOS = []


def registrar_evento(evento):
    EVENTOS.append(evento)

Pruebas:

import pytest

from app.eventos import EVENTOS, registrar_evento


@pytest.fixture
def eventos_limpios():
    EVENTOS.clear()
    yield EVENTOS
    EVENTOS.clear()


def test_registrar_evento_agrega_elemento(eventos_limpios):
    registrar_evento("usuario_creado")

    assert eventos_limpios == ["usuario_creado"]


def test_registrar_dos_eventos_conserva_orden(eventos_limpios):
    registrar_evento("usuario_creado")
    registrar_evento("email_enviado")

    assert eventos_limpios == ["usuario_creado", "email_enviado"]


def test_eventos_limpios_comienza_vacio(eventos_limpios):
    assert eventos_limpios == []

Ejecuta:

python -m pytest tests/test_eventos.py

27.22 Lista de verificación

Antes de continuar con el próximo tema, verifica lo siguiente:

  • Las pruebas crean sus propios datos.
  • Los archivos temporales usan tmp_path.
  • Los recursos compartidos se limpian con fixtures.
  • Las variables de entorno se modifican con monkeypatch.
  • Los archivos abiertos se cierran correctamente.
  • No se borran archivos importantes del proyecto desde pruebas.
  • La suite se ejecuta correctamente con python -m pytest.

27.23 Conclusión

En este tema vimos cómo limpiar datos, archivos y recursos después de cada prueba. La limpieza correcta mantiene la suite aislada, repetible y preparada para ejecución completa o paralela.

En el próximo tema trabajaremos con diagnóstico de fallas en suites automatizadas.