Algunos programas muestran mensajes con print y piden datos con input. Si probamos esos programas manualmente, tenemos que escribir respuestas y mirar la pantalla cada vez.
En este tema aprenderemos a automatizar esas pruebas con pytest, usando capsys para capturar la salida y monkeypatch para simular la entrada del usuario.
Crea un proyecto nuevo:
mkdir pytest-consola-demo
cd pytest-consola-demo
Si pytest no está instalado en el entorno activo:
python -m pip install pytest
Crea un archivo llamado consola.py:
def mostrar_saludo(nombre):
print(f"Hola, {nombre}")
def pedir_nombre():
return input("Nombre: ").strip()
def pedir_edad():
texto = input("Edad: ")
return int(texto)
def mostrar_total(importes):
total = sum(importes)
print(f"Total: {total}")
def confirmar_operacion():
respuesta = input("Confirmar (s/n): ").strip().lower()
return respuesta == "s"
def ejecutar_registro():
nombre = pedir_nombre()
edad = pedir_edad()
print(f"{nombre} tiene {edad} años")
return {"nombre": nombre, "edad": edad}
El módulo combina funciones que imprimen, funciones que leen datos y una función que ejecuta un flujo pequeño.
capsys es una fixture de pytest que permite capturar lo que se imprime por consola.
from consola import mostrar_saludo
def test_mostrar_saludo(capsys):
mostrar_saludo("Ana")
salida = capsys.readouterr()
assert salida.out == "Hola, Ana\n"
salida.out contiene lo escrito en la salida estándar. El salto de línea aparece porque print lo agrega automáticamente.
También podemos capturar resultados de funciones que imprimen cálculos:
from consola import mostrar_total
def test_mostrar_total(capsys):
mostrar_total([100, 200, 50])
salida = capsys.readouterr()
assert salida.out == "Total: 350\n"
Si la salida tiene varias líneas, conviene compararla como texto completo o usar splitlines().
monkeypatch permite reemplazar temporalmente objetos durante una prueba. Para simular input, reemplazamos builtins.input:
from consola import pedir_nombre
def test_pedir_nombre(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: " Ana ")
resultado = pedir_nombre()
assert resultado == "Ana"
La función recibe una respuesta falsa, como si el usuario hubiera escrito Ana.
Si una función convierte el texto ingresado, podemos probar el resultado convertido:
from consola import pedir_edad
def test_pedir_edad(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: "25")
resultado = pedir_edad()
assert resultado == 25
La prueba no necesita pausar la ejecución ni esperar que alguien escriba en la terminal.
Si el usuario escribe un valor que no se puede convertir a número, int lanza ValueError:
import pytest
def test_pedir_edad_con_texto_invalido(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: "abc")
with pytest.raises(ValueError):
pedir_edad()
Esta prueba documenta qué ocurre cuando la entrada no tiene el formato esperado.
Para probar varias respuestas de confirmación:
from consola import confirmar_operacion
@pytest.mark.parametrize("texto, esperado", [
("s", True),
("S", True),
("n", False),
])
def test_confirmar_operacion(monkeypatch, texto, esperado):
monkeypatch.setattr("builtins.input", lambda mensaje: texto)
assert confirmar_operacion() is esperado
La parametrización evita repetir tres funciones de prueba casi iguales.
Cuando una función llama a input más de una vez, podemos usar un iterador:
from consola import ejecutar_registro
def test_ejecutar_registro(monkeypatch, capsys):
respuestas = iter(["Ana", "25"])
monkeypatch.setattr("builtins.input", lambda mensaje: next(respuestas))
resultado = ejecutar_registro()
salida = capsys.readouterr()
assert resultado == {"nombre": "Ana", "edad": 25}
assert salida.out == "Ana tiene 25 años\n"
La primera llamada a input devuelve Ana y la segunda devuelve 25.
En estas pruebas reemplazamos input por una función falsa. Esa función recibe el mensaje como argumento, pero no lo imprime en pantalla.
El archivo test_consola.py puede quedar así:
import pytest
from consola import (
confirmar_operacion,
ejecutar_registro,
mostrar_saludo,
mostrar_total,
pedir_edad,
pedir_nombre,
)
def test_mostrar_saludo(capsys):
mostrar_saludo("Ana")
salida = capsys.readouterr()
assert salida.out == "Hola, Ana\n"
def test_mostrar_total(capsys):
mostrar_total([100, 200, 50])
salida = capsys.readouterr()
assert salida.out == "Total: 350\n"
def test_pedir_nombre(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: " Ana ")
resultado = pedir_nombre()
assert resultado == "Ana"
def test_pedir_edad(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: "25")
resultado = pedir_edad()
assert resultado == 25
def test_pedir_edad_con_texto_invalido(monkeypatch):
monkeypatch.setattr("builtins.input", lambda mensaje: "abc")
with pytest.raises(ValueError):
pedir_edad()
@pytest.mark.parametrize("texto, esperado", [
("s", True),
("S", True),
("n", False),
])
def test_confirmar_operacion(monkeypatch, texto, esperado):
monkeypatch.setattr("builtins.input", lambda mensaje: texto)
assert confirmar_operacion() is esperado
def test_ejecutar_registro(monkeypatch, capsys):
respuestas = iter(["Ana", "25"])
monkeypatch.setattr("builtins.input", lambda mensaje: next(respuestas))
resultado = ejecutar_registro()
salida = capsys.readouterr()
assert resultado == {"nombre": "Ana", "edad": 25}
assert salida.out == "Ana tiene 25 años\n"
Desde la raíz del proyecto, ejecuta:
python -m pytest
La salida esperada será similar a:
collected 9 items
test_consola.py ......... [100%]
9 passed in 0.04s
Normalmente pytest captura la salida de consola. Si quieres verla mientras se ejecuta la prueba, puedes usar -s:
python -m pytest -s
Esto es útil para investigar, pero no debe reemplazar las aserciones.
Una práctica recomendable es separar las funciones que calculan de las funciones que imprimen o leen datos. Por ejemplo, esta función es más fácil de probar:
def formatear_registro(nombre, edad):
return f"{nombre} tiene {edad} años"
Luego la parte de consola solo se encarga de mostrar ese resultado:
print(formatear_registro(nombre, edad))
Cuanta menos lógica haya dentro de la interacción con consola, más simples serán las pruebas.
print agrega \n al final.assert.mkdir pytest-consola-demo
cd pytest-consola-demo
python -m pip install pytest
python -m pytest
python -m pytest -v
python -m pytest -s
python -m pytest test_consola.py::test_ejecutar_registro -v
capsys captura lo que se imprime por consola.monkeypatch permite reemplazar input durante una prueba.iter y next.En este tema probamos código que usa print e input. Con capsys podemos verificar salidas, y con monkeypatch podemos simular respuestas del usuario sin detener la ejecución.
En el próximo tema veremos dobles de prueba: stubs, fakes y mocks, una herramienta central para reemplazar dependencias durante una prueba.