12. Escribir pruebas simples con pytest

12.1 Objetivo del tema

En el tema anterior instalamos pytest y ejecutamos una primera prueba. Ahora vamos a escribir pruebas simples con más variedad: números, cadenas, booleanos, listas, diccionarios y excepciones.

El objetivo es acostumbrarnos al estilo de pytest: funciones con nombre test_..., aserciones con assert y ejecución desde la terminal con python -m pytest.

Idea clave: en pytest una prueba simple suele ser una función que prepara datos, llama al código y usa assert.

12.2 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir pytest-pruebas-simples-demo
cd pytest-pruebas-simples-demo

Si todavía no tienes pytest instalado en el entorno activo, instálalo con:

python -m pip install pytest

12.3 Crear el módulo a probar

Crea un archivo llamado servicios.py:

def calcular_total(precio, cantidad):
    return precio * cantidad


def normalizar_email(email):
    return email.strip().lower()


def es_usuario_activo(usuario):
    return usuario.get("activo", False)


def obtener_nombres(productos):
    return [producto["nombre"] for producto in productos]


def crear_resumen_producto(nombre, precio):
    return {
        "nombre": nombre.strip().title(),
        "precio": precio,
        "disponible": precio > 0,
    }


def aplicar_cupon(total, cupon):
    if cupon == "DESC10":
        return total * 0.9
    if cupon == "DESC20":
        return total * 0.8
    raise ValueError("Cupón inválido")

Estas funciones son pequeñas y nos permiten practicar distintos tipos de aserciones.

12.4 Crear el archivo de pruebas

Crea un archivo llamado test_servicios.py:

from servicios import calcular_total


def test_calcular_total():
    resultado = calcular_total(1000, 3)

    assert resultado == 3000

La función de prueba comienza con test_. Dentro usamos assert para comparar el resultado obtenido con el resultado esperado.

12.5 Ejecutar la primera prueba

Desde la raíz del proyecto, ejecuta:

python -m pytest

La salida esperada será similar a:

collected 1 item

test_servicios.py .                                              [100%]

1 passed in 0.02s

12.6 Probar cadenas

Importa normalizar_email y agrega una prueba:

from servicios import calcular_total, normalizar_email


def test_normalizar_email():
    resultado = normalizar_email("  ANA@EXAMPLE.COM  ")

    assert resultado == "ana@example.com"

La entrada tiene espacios y mayúsculas para demostrar claramente qué transformación esperamos.

12.7 Probar booleanos

Para valores booleanos podemos usar is True o is False cuando queremos verificar exactamente un booleano:

from servicios import es_usuario_activo


def test_usuario_activo_devuelve_true():
    usuario = {"nombre": "Ana", "activo": True}

    assert es_usuario_activo(usuario) is True


def test_usuario_sin_activo_devuelve_false():
    usuario = {"nombre": "Luis"}

    assert es_usuario_activo(usuario) is False

El segundo caso confirma el valor por defecto cuando falta la clave activo.

12.8 Probar listas

Las listas pueden compararse directamente con assert:

from servicios import obtener_nombres


def test_obtener_nombres():
    productos = [
        {"nombre": "Teclado", "precio": 50000},
        {"nombre": "Mouse", "precio": 12000},
    ]

    resultado = obtener_nombres(productos)

    assert resultado == ["Teclado", "Mouse"]

En esta comparación importa tanto el contenido como el orden.

12.9 Probar lista vacía

También conviene probar colecciones vacías:

def test_obtener_nombres_con_lista_vacia():
    resultado = obtener_nombres([])

    assert resultado == []

Esta prueba confirma que la función no falla cuando no hay productos.

12.10 Probar diccionarios

Para diccionarios podemos comparar toda la estructura esperada:

from servicios import crear_resumen_producto


def test_crear_resumen_producto():
    resultado = crear_resumen_producto("  teclado  ", 50000)

    assert resultado == {
        "nombre": "Teclado",
        "precio": 50000,
        "disponible": True,
    }

Esta prueba verifica normalización del nombre, precio y disponibilidad.

12.11 Probar una parte del diccionario

Si queremos enfocarnos en una regla específica, podemos comprobar una sola clave:

def test_producto_con_precio_cero_no_esta_disponible():
    resultado = crear_resumen_producto("mouse", 0)

    assert resultado["disponible"] is False

Esta prueba se concentra en la regla de disponibilidad.

12.12 Probar excepciones

Para probar errores esperados usamos pytest.raises:

import pytest

from servicios import aplicar_cupon


def test_cupon_invalido_lanza_error():
    with pytest.raises(ValueError):
        aplicar_cupon(1000, "NO_EXISTE")

La prueba pasa si la función lanza ValueError.

12.13 Probar el mensaje de error

Podemos guardar la excepción para revisar su mensaje:

def test_cupon_invalido_muestra_mensaje_claro():
    with pytest.raises(ValueError) as error:
        aplicar_cupon(1000, "NO_EXISTE")

    assert str(error.value) == "Cupón inválido"

Conviene comprobar mensajes solo cuando forman parte importante del comportamiento esperado.

12.14 Archivo completo de pruebas

El archivo test_servicios.py puede quedar así:

import pytest

from servicios import (
    aplicar_cupon,
    calcular_total,
    crear_resumen_producto,
    es_usuario_activo,
    normalizar_email,
    obtener_nombres,
)


def test_calcular_total():
    resultado = calcular_total(1000, 3)
    assert resultado == 3000


def test_normalizar_email():
    resultado = normalizar_email("  ANA@EXAMPLE.COM  ")
    assert resultado == "ana@example.com"


def test_usuario_activo_devuelve_true():
    usuario = {"nombre": "Ana", "activo": True}
    assert es_usuario_activo(usuario) is True


def test_usuario_sin_activo_devuelve_false():
    usuario = {"nombre": "Luis"}
    assert es_usuario_activo(usuario) is False


def test_obtener_nombres():
    productos = [
        {"nombre": "Teclado", "precio": 50000},
        {"nombre": "Mouse", "precio": 12000},
    ]
    resultado = obtener_nombres(productos)
    assert resultado == ["Teclado", "Mouse"]


def test_obtener_nombres_con_lista_vacia():
    resultado = obtener_nombres([])
    assert resultado == []


def test_crear_resumen_producto():
    resultado = crear_resumen_producto("  teclado  ", 50000)
    assert resultado == {
        "nombre": "Teclado",
        "precio": 50000,
        "disponible": True,
    }


def test_producto_con_precio_cero_no_esta_disponible():
    resultado = crear_resumen_producto("mouse", 0)
    assert resultado["disponible"] is False


def test_cupon_invalido_lanza_error():
    with pytest.raises(ValueError):
        aplicar_cupon(1000, "NO_EXISTE")


def test_cupon_invalido_muestra_mensaje_claro():
    with pytest.raises(ValueError) as error:
        aplicar_cupon(1000, "NO_EXISTE")

    assert str(error.value) == "Cupón inválido"

12.15 Ejecutar todas las pruebas

Ejecuta:

python -m pytest

La salida esperada será similar a:

collected 10 items

test_servicios.py ..........                                     [100%]

10 passed in 0.03s

12.16 Ejecutar con salida detallada

Para ver cada prueba por nombre:

python -m pytest -v

La opción -v ayuda a revisar rápidamente qué casos fueron ejecutados.

12.17 Entender una prueba simple

Una prueba simple suele tener tres partes:

Parte Qué hace Ejemplo
Preparación Define datos de entrada usuario = {"activo": True}
Ejecución Llama a la función resultado = es_usuario_activo(usuario)
Verificación Comprueba el resultado assert resultado is True

12.18 Escribir nombres claros

Los nombres de las pruebas deben describir qué comportamiento se espera:

def test_usuario_sin_activo_devuelve_false():
    usuario = {"nombre": "Luis"}

    assert es_usuario_activo(usuario) is False

Este nombre permite entender la regla sin leer toda la implementación.

12.19 Errores frecuentes

  • Olvidar importar la función a probar: la prueba fallará con NameError.
  • Nombrar la función sin test_: pytest no la ejecutará.
  • Usar entradas que no prueban la transformación: por ejemplo normalizar un email que ya está normalizado.
  • Comparar listas sin considerar el orden: assert compara el orden de los elementos.
  • Probar muchas reglas no relacionadas en una sola función: dificulta leer la falla.

12.20 Comandos usados en este tema

mkdir pytest-pruebas-simples-demo
cd pytest-pruebas-simples-demo
python -m pip install pytest
python -m pytest
python -m pytest -v
python -m pytest test_servicios.py

12.21 Qué debes recordar de este tema

  • Con pytest podemos escribir pruebas como funciones simples.
  • Las funciones de prueba deben comenzar con test_.
  • assert permite comparar números, cadenas, listas y diccionarios.
  • pytest.raises permite probar excepciones esperadas.
  • Una prueba clara prepara datos, ejecuta una función y verifica el resultado.
  • Los nombres deben explicar el comportamiento probado.

12.22 Conclusión

En este tema escribimos pruebas simples con pytest para distintos tipos de resultados. Practicamos assert, pytest.raises y nombres descriptivos.

En el próximo tema veremos con más detalle cómo funcionan las aserciones con assert y cómo interpretar las fallas que muestra pytest.