Muchas aplicaciones leen o escriben archivos. Para probar ese comportamiento no conviene usar archivos reales del proyecto, porque podríamos modificarlos, borrarlos o dejar basura entre ejecuciones.
En este tema usaremos archivos temporales y directorios de trabajo controlados con pytest. Trabajaremos principalmente con la fixture tmp_path y con pathlib.
Los archivos temporales permiten que cada prueba tenga su propio espacio de trabajo. Así evitamos que una prueba dependa de archivos dejados por otra.
Beneficios:
Crea app/archivos.py:
def leer_texto(ruta):
return ruta.read_text(encoding="utf-8")
def escribir_texto(ruta, contenido):
ruta.write_text(contenido, encoding="utf-8")
def contar_lineas(ruta):
contenido = ruta.read_text(encoding="utf-8")
return len(contenido.splitlines())
Estas funciones reciben una ruta y trabajan con archivos de texto.
Crea tests/test_archivos.py:
from app.archivos import leer_texto
def test_leer_texto_devuelve_contenido_de_archivo_temporal(tmp_path):
ruta = tmp_path / "mensaje.txt"
ruta.write_text("Hola pytest", encoding="utf-8")
assert leer_texto(ruta) == "Hola pytest"
tmp_path es una fixture de pytest que entrega una carpeta temporal como objeto Path.
Ejecuta:
python -m pytest tests/test_archivos.py
pytest crea una carpeta temporal para la prueba. No necesitas crear ni borrar manualmente mensaje.txt en el proyecto.
Agrega esta prueba:
from app.archivos import escribir_texto
def test_escribir_texto_crea_archivo_con_contenido(tmp_path):
ruta = tmp_path / "salida.txt"
escribir_texto(ruta, "Contenido generado")
assert ruta.read_text(encoding="utf-8") == "Contenido generado"
La prueba verifica que la función escriba el contenido esperado.
Agrega una prueba para contar_lineas:
from app.archivos import contar_lineas
def test_contar_lineas_devuelve_cantidad_de_lineas(tmp_path):
ruta = tmp_path / "lineas.txt"
ruta.write_text("uno\ndos\ntres\n", encoding="utf-8")
assert contar_lineas(ruta) == 3
También podemos crear carpetas dentro de tmp_path:
def test_escribir_texto_en_subdirectorio_temporal(tmp_path):
carpeta = tmp_path / "reportes"
carpeta.mkdir()
ruta = carpeta / "reporte.txt"
escribir_texto(ruta, "Reporte generado")
assert ruta.exists()
assert ruta.read_text(encoding="utf-8") == "Reporte generado"
Esto permite probar código que organiza archivos en subcarpetas.
Agrega esta función en app/archivos.py:
def guardar_reporte(carpeta, nombre, contenido):
carpeta.mkdir(parents=True, exist_ok=True)
ruta = carpeta / nombre
ruta.write_text(contenido, encoding="utf-8")
return ruta
La función crea la carpeta si no existe, guarda el archivo y devuelve la ruta generada.
Prueba guardar_reporte:
from app.archivos import guardar_reporte
def test_guardar_reporte_crea_carpeta_y_archivo(tmp_path):
carpeta = tmp_path / "reportes"
ruta = guardar_reporte(carpeta, "resultado.txt", "Todo correcto")
assert ruta.exists()
assert ruta.name == "resultado.txt"
assert ruta.read_text(encoding="utf-8") == "Todo correcto"
Podemos crear una fixture que prepare un archivo temporal:
import pytest
@pytest.fixture
def archivo_con_lineas(tmp_path):
ruta = tmp_path / "lineas.txt"
ruta.write_text("uno\ndos\ntres\n", encoding="utf-8")
return ruta
Uso:
def test_contar_lineas_con_fixture_devuelve_cantidad(archivo_con_lineas):
assert contar_lineas(archivo_con_lineas) == 3
Si varias pruebas necesitan el mismo archivo temporal, puedes mover la fixture a tests/conftest.py:
import pytest
@pytest.fixture
def archivo_con_lineas(tmp_path):
ruta = tmp_path / "lineas.txt"
ruta.write_text("uno\ndos\ntres\n", encoding="utf-8")
return ruta
Luego cualquier prueba puede recibir archivo_con_lineas como parámetro.
Podemos crear JSON temporal dentro de una prueba:
import json
def test_leer_json_temporal(tmp_path):
ruta = tmp_path / "usuario.json"
datos = {"nombre": "Ana", "activo": True}
ruta.write_text(json.dumps(datos), encoding="utf-8")
contenido = json.loads(ruta.read_text(encoding="utf-8"))
assert contenido["nombre"] == "Ana"
assert contenido["activo"] is True
Esto sirve para probar lectores de configuración, importadores o validadores de archivos JSON.
También podemos probar errores esperados:
import pytest
from app.archivos import leer_texto
def test_leer_texto_con_archivo_inexistente_lanza_file_not_found_error(tmp_path):
ruta = tmp_path / "no_existe.txt"
with pytest.raises(FileNotFoundError):
leer_texto(ruta)
La ruta apunta a un archivo que no creamos dentro del directorio temporal.
Algunos códigos trabajan con rutas relativas al directorio actual. pytest ofrece monkeypatch.chdir para cambiarlo durante una prueba.
from pathlib import Path
def test_archivo_en_directorio_actual(tmp_path, monkeypatch):
monkeypatch.chdir(tmp_path)
Path("entrada.txt").write_text("dato", encoding="utf-8")
assert Path("entrada.txt").read_text(encoding="utf-8") == "dato"
Al terminar la prueba, pytest restaura el estado correspondiente.
No conviene escribir pruebas que dependan de rutas absolutas del equipo:
ruta = "C:/usuarios/ana/documentos/datos.txt"
Ese archivo puede no existir en otra computadora. Es mejor usar tmp_path o rutas relativas controladas dentro del proyecto.
Evita que una prueba genere archivos como salida.txt directamente en la raíz del proyecto. Eso ensucia el directorio y puede afectar otras ejecuciones.
En lugar de esto:
ruta = Path("salida.txt")
usa:
ruta = tmp_path / "salida.txt"
Usa archivos fijos en tests/data cuando representan casos de prueba estables que quieres conservar. Usa tmp_path cuando el archivo se crea durante la prueba o cuando quieres verificar escritura.
tests/data: datos conocidos y versionados.tmp_path: archivos generados, modificados o descartables.tmp_path en lugar de escribir en la raíz.encoding="utf-8".Crea en app/archivos.py una función copiar_archivo que reciba una ruta de origen y una ruta de destino. La función debe leer el texto del origen, escribirlo en el destino y devolver la ruta de destino.
Luego crea pruebas con tmp_path para verificar:
FileNotFoundError.Función en app/archivos.py:
def copiar_archivo(origen, destino):
contenido = origen.read_text(encoding="utf-8")
destino.write_text(contenido, encoding="utf-8")
return destino
Pruebas:
import pytest
from app.archivos import copiar_archivo
def test_copiar_archivo_crea_archivo_destino(tmp_path):
origen = tmp_path / "origen.txt"
destino = tmp_path / "destino.txt"
origen.write_text("contenido", encoding="utf-8")
copiar_archivo(origen, destino)
assert destino.exists()
def test_copiar_archivo_copia_el_contenido(tmp_path):
origen = tmp_path / "origen.txt"
destino = tmp_path / "destino.txt"
origen.write_text("contenido", encoding="utf-8")
copiar_archivo(origen, destino)
assert destino.read_text(encoding="utf-8") == "contenido"
def test_copiar_archivo_con_origen_inexistente_lanza_error(tmp_path):
origen = tmp_path / "no_existe.txt"
destino = tmp_path / "destino.txt"
with pytest.raises(FileNotFoundError):
copiar_archivo(origen, destino)
Ejecuta:
python -m pytest tests/test_archivos.py
Antes de continuar con el próximo tema, verifica lo siguiente:
tmp_path para archivos generados durante la prueba.Path para construir rutas.encoding="utf-8" al leer y escribir texto.python -m pytest.En este tema automatizamos pruebas con archivos temporales y directorios de trabajo. Usar tmp_path permite crear escenarios seguros y aislados sin afectar archivos reales del proyecto.
En el próximo tema trabajaremos con variables de entorno y configuración por ambiente, una necesidad común en suites automatizadas que deben comportarse de forma distinta según el contexto.