Una prueba automatizada no siempre verifica solo valores de retorno. A veces necesitamos comprobar qué se imprime en pantalla, qué mensaje trae una excepción o qué registro queda en los logs.
En este tema usaremos herramientas de pytest para capturar salida por consola, validar mensajes de error y revisar logs de forma controlada.
En automatización de pruebas podemos verificar distintos tipos de salida:
print.stderr.logging.Crea app/consola.py:
def mostrar_resumen(nombre, total):
print(f"Usuario: {nombre}")
print(f"Total: {total}")
def mostrar_error(mensaje):
print(f"ERROR: {mensaje}")
Estas funciones imprimen texto en la salida estándar.
pytest ofrece la fixture capsys para capturar lo que se imprime durante una prueba.
from app.consola import mostrar_resumen
def test_mostrar_resumen_imprime_usuario_y_total(capsys):
mostrar_resumen("Ana", 350)
salida = capsys.readouterr()
assert "Usuario: Ana" in salida.out
assert "Total: 350" in salida.out
salida.out contiene lo impreso por salida estándar.
Crea tests/test_consola.py con la prueba anterior y ejecuta:
python -m pytest tests/test_consola.py
La prueba pasa si los textos esperados fueron impresos.
Si el orden importa, podemos dividir la salida en líneas:
def test_mostrar_resumen_imprime_lineas_esperadas(capsys):
mostrar_resumen("Ana", 350)
salida = capsys.readouterr()
lineas = salida.out.strip().splitlines()
assert lineas == [
"Usuario: Ana",
"Total: 350",
]
Esta prueba es más estricta: valida contenido y orden.
Si una función escribe en stderr, capsys también puede capturarlo. Modifica app/consola.py:
import sys
def mostrar_error_stderr(mensaje):
print(f"ERROR: {mensaje}", file=sys.stderr)
Prueba:
from app.consola import mostrar_error_stderr
def test_mostrar_error_stderr_escribe_en_salida_de_error(capsys):
mostrar_error_stderr("archivo no encontrado")
salida = capsys.readouterr()
assert salida.out == ""
assert "ERROR: archivo no encontrado" in salida.err
Con pytest.raises podemos comprobar el tipo de excepción y su mensaje.
def dividir(a, b):
if b == 0:
raise ValueError("No se puede dividir por cero")
return a / b
Prueba:
import pytest
def test_dividir_con_divisor_cero_lanza_mensaje_claro():
with pytest.raises(ValueError, match="No se puede dividir por cero"):
dividir(10, 0)
match verifica que el mensaje de la excepción coincida con el texto indicado.
También podemos capturar la excepción para revisar detalles:
def test_dividir_con_divisor_cero_permite_revisar_excepcion():
with pytest.raises(ValueError) as error:
dividir(10, 0)
assert str(error.value) == "No se puede dividir por cero"
Esta forma es útil cuando queremos varias aserciones sobre el error.
Crea app/procesos.py:
import logging
logger = logging.getLogger(__name__)
def procesar_pago(monto):
if monto <= 0:
logger.error("Monto inválido para pago: %s", monto)
raise ValueError("El monto debe ser mayor a cero")
logger.info("Pago procesado por monto: %s", monto)
return True
El módulo registra mensajes informativos y errores.
pytest ofrece caplog para inspeccionar logs generados durante una prueba.
import logging
from app.procesos import procesar_pago
def test_procesar_pago_valido_registra_mensaje_info(caplog):
with caplog.at_level(logging.INFO):
procesar_pago(100)
assert "Pago procesado por monto: 100" in caplog.text
caplog.text contiene los mensajes capturados.
Podemos combinar excepción y log:
import logging
import pytest
from app.procesos import procesar_pago
def test_procesar_pago_invalido_registra_error_y_lanza_excepcion(caplog):
with caplog.at_level(logging.ERROR):
with pytest.raises(ValueError, match="El monto debe ser mayor a cero"):
procesar_pago(0)
assert "Monto inválido para pago: 0" in caplog.text
La prueba valida tanto el error lanzado como el log generado.
caplog.records permite inspeccionar cada registro:
def test_procesar_pago_invalido_registra_nivel_error(caplog):
with caplog.at_level(logging.ERROR):
with pytest.raises(ValueError):
procesar_pago(0)
assert caplog.records[0].levelname == "ERROR"
assert caplog.records[0].message == "Monto inválido para pago: 0"
Esto es más preciso que buscar texto en toda la salida.
No todos los logs merecen una prueba. Conviene verificar mensajes cuando son parte del diagnóstico esperado, auditoría o comportamiento importante.
Evita pruebas demasiado frágiles que fallen por cambiar una palabra sin afectar el comportamiento real.
Los mensajes de error deben ayudar a entender el problema. Por ejemplo:
raise ValueError("El monto debe ser mayor a cero")
Es mejor que:
raise ValueError("Error")
Las pruebas pueden ayudarnos a mantener mensajes útiles para usuarios o desarrolladores.
Podemos probar varios casos inválidos con parametrización:
import pytest
@pytest.mark.parametrize("monto", [0, -10])
def test_procesar_pago_con_montos_invalidos_lanza_mensaje_claro(monto):
with pytest.raises(ValueError, match="El monto debe ser mayor a cero"):
procesar_pago(monto)
A veces queremos comprobar que una función no imprime nada:
def sumar(a, b):
return a + b
def test_sumar_no_imprime_salida(capsys):
assert sumar(2, 3) == 5
salida = capsys.readouterr()
assert salida.out == ""
assert salida.err == ""
Esto puede servir para detectar impresiones de depuración que quedaron por error.
capsys.readouterr() después de ejecutar la función.caplog.at_level.in si solo importa una parte del mensaje.match usa expresión regular.Crea app/importador.py con una función importar_usuario. La función debe:
nombre y email.Importando usuario: nombre.ValueError con mensaje claro si falta el email.Luego crea pruebas para salida por consola, log y mensaje de error.
Archivo app/importador.py:
import logging
logger = logging.getLogger(__name__)
def importar_usuario(usuario):
if not usuario.get("email"):
logger.error("Usuario sin email: %s", usuario.get("nombre"))
raise ValueError("El usuario debe tener email")
print(f"Importando usuario: {usuario['nombre']}")
logger.info("Usuario importado: %s", usuario["email"])
return True
Pruebas:
import logging
import pytest
from app.importador import importar_usuario
def test_importar_usuario_imprime_nombre(capsys):
importar_usuario({"nombre": "Ana", "email": "ana@example.com"})
salida = capsys.readouterr()
assert "Importando usuario: Ana" in salida.out
def test_importar_usuario_registra_log_info(caplog):
with caplog.at_level(logging.INFO):
importar_usuario({"nombre": "Ana", "email": "ana@example.com"})
assert "Usuario importado: ana@example.com" in caplog.text
def test_importar_usuario_sin_email_lanza_mensaje_claro(caplog):
with caplog.at_level(logging.ERROR):
with pytest.raises(ValueError, match="El usuario debe tener email"):
importar_usuario({"nombre": "Ana"})
assert "Usuario sin email: Ana" in caplog.text
Ejecuta:
python -m pytest tests/test_importador.py
Antes de continuar con el próximo tema, verifica lo siguiente:
capsys.capsys.pytest.raises.caplog.python -m pytest.En este tema automatizamos verificaciones sobre salida por consola, logs y mensajes de error. Estas pruebas ayudan a controlar diagnósticos importantes y mensajes que facilitan la comprensión de fallas.
En el próximo tema veremos cómo lograr pruebas determinísticas controlando fechas, aleatoriedad y dependencias externas.