23. Probar entrada y salida por consola

23.1 Objetivo del tema

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.

Idea clave: el código de consola también se puede probar, pero conviene separar la lógica de negocio de la interacción con el usuario.

23.2 Crear una carpeta de práctica

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

23.3 Crear el módulo a probar

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.

23.4 Capturar salida con capsys

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.

23.5 Probar texto impreso con varias líneas

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().

23.6 Simular input con monkeypatch

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.

23.7 Probar conversión de datos ingresados

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.

23.8 Probar entrada inválida

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.

23.9 Parametrizar respuestas de consola

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.

23.10 Simular varias entradas

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.

23.11 Sobre los mensajes de input

En estas pruebas reemplazamos input por una función falsa. Esa función recibe el mensaje como argumento, pero no lo imprime en pantalla.

Si quieres probar textos de ayuda o mensajes complejos, es mejor moverlos a funciones separadas que devuelvan texto y probar esas funciones directamente.

23.12 Archivo completo de pruebas

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"

23.13 Ejecutar las pruebas

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

23.14 Ver la salida real durante una prueba

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.

23.15 Mejor diseño para código interactivo

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.

23.16 Errores frecuentes

  • Olvidar el salto de línea: print agrega \n al final.
  • Usar input real en una prueba: la prueba queda bloqueada esperando datos.
  • Probar demasiado flujo interactivo junto: conviene separar lógica y consola.
  • No probar entradas inválidas: los usuarios pueden escribir valores inesperados.
  • Abusar de print para depurar: una prueba debe verificar resultados con assert.

23.17 Comandos usados en este tema

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

23.18 Qué debes recordar de este tema

  • capsys captura lo que se imprime por consola.
  • monkeypatch permite reemplazar input durante una prueba.
  • Para varias entradas, puedes usar iter y next.
  • Las pruebas no deben depender de interacción manual.
  • Separar lógica y consola simplifica el testing.
  • Las entradas inválidas también deben tener pruebas.

23.19 Conclusión

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.