13. Aserciones con assert e interpretación de fallas en pytest

13.1 Objetivo del tema

En pytest, la mayoría de las comprobaciones se escriben con la palabra clave assert. A diferencia de unittest, no necesitamos llamar métodos como assertEqual o assertTrue.

En este tema veremos cómo usar assert para comparar distintos valores y cómo leer los mensajes que muestra pytest cuando una prueba falla.

Idea clave: pytest convierte un assert fallido en un reporte detallado que muestra qué expresión falló y qué valores participaron.

13.2 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir pytest-assert-demo
cd pytest-assert-demo

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

python -m pip install pytest

13.3 Crear el código a probar

Crea un archivo llamado analisis.py:

def calcular_promedio(valores):
    if not valores:
        return 0
    return sum(valores) / len(valores)


def normalizar_etiqueta(texto):
    return texto.strip().lower().replace(" ", "-")


def obtener_aprobados(estudiantes):
    return [estudiante for estudiante in estudiantes if estudiante["nota"] >= 6]


def resumir_estudiante(nombre, nota):
    return {
        "nombre": nombre.strip().title(),
        "nota": nota,
        "aprobado": nota >= 6,
    }


def validar_nota(nota):
    if nota < 0 or nota > 10:
        raise ValueError("La nota debe estar entre 0 y 10")
    return nota

Usaremos estas funciones para probar números, cadenas, listas, diccionarios, booleanos y excepciones.

13.4 Crear el archivo de pruebas

Crea test_analisis.py:

from analisis import calcular_promedio


def test_calcular_promedio():
    resultado = calcular_promedio([8, 6, 10])

    assert resultado == 8

Esta es una aserción básica: si resultado vale 8, la prueba pasa.

13.5 Ejecutar la prueba

Ejecuta:

python -m pytest

La salida esperada será similar a:

collected 1 item

test_analisis.py .                                               [100%]

1 passed in 0.02s

13.6 Comparar números

Para números enteros o resultados exactos usamos ==:

def test_promedio_de_lista_vacia_es_cero():
    resultado = calcular_promedio([])

    assert resultado == 0

Si la función devolviera otro valor, pytest mostraría el valor obtenido y la expresión que falló.

13.7 Comparar cadenas

Importa normalizar_etiqueta y agrega esta prueba:

from analisis import normalizar_etiqueta


def test_normalizar_etiqueta():
    resultado = normalizar_etiqueta("  Curso Python  ")

    assert resultado == "curso-python"

Cuando fallan cadenas largas, pytest ayuda a ver diferencias entre el texto esperado y el obtenido.

13.8 Comparar booleanos

Para comprobar un booleano exacto, usa is True o is False:

from analisis import resumir_estudiante


def test_estudiante_con_nota_6_esta_aprobado():
    estudiante = resumir_estudiante("Ana", 6)

    assert estudiante["aprobado"] is True


def test_estudiante_con_nota_5_no_esta_aprobado():
    estudiante = resumir_estudiante("Luis", 5)

    assert estudiante["aprobado"] is False

Esto deja claro que esperamos exactamente un valor booleano.

13.9 Comparar listas

Las listas se comparan por contenido y por orden:

from analisis import obtener_aprobados


def test_obtener_aprobados():
    estudiantes = [
        {"nombre": "Ana", "nota": 8},
        {"nombre": "Luis", "nota": 5},
        {"nombre": "Marta", "nota": 7},
    ]

    resultado = obtener_aprobados(estudiantes)

    assert resultado == [
        {"nombre": "Ana", "nota": 8},
        {"nombre": "Marta", "nota": 7},
    ]

Si falta un elemento, sobra otro o cambia el orden, la aserción falla.

13.10 Comparar diccionarios

Los diccionarios pueden compararse completos:

def test_resumir_estudiante():
    resultado = resumir_estudiante("  ana  ", 9)

    assert resultado == {
        "nombre": "Ana",
        "nota": 9,
        "aprobado": True,
    }

Cuando un diccionario falla, pytest suele mostrar qué claves o valores son diferentes.

13.11 Comprobar pertenencia

Podemos usar in dentro de un assert:

def test_nombre_normalizado_contiene_guion():
    resultado = normalizar_etiqueta("Curso Python")

    assert "-" in resultado

También sirve para listas, tuplas, conjuntos y claves de diccionarios.

13.12 Comprobar excepciones

Para errores esperados seguimos usando pytest.raises:

import pytest

from analisis import validar_nota


def test_nota_mayor_a_10_lanza_error():
    with pytest.raises(ValueError):
        validar_nota(11)

Si no se lanza la excepción, la prueba falla.

13.13 Comprobar mensaje de excepción

Para revisar el mensaje:

def test_nota_invalida_muestra_mensaje_claro():
    with pytest.raises(ValueError) as error:
        validar_nota(-1)

    assert str(error.value) == "La nota debe estar entre 0 y 10"

Usa esta técnica cuando el texto del error sea relevante para el proyecto.

13.14 Provocar una falla para leerla

Cambia temporalmente una expectativa para que sea incorrecta:

def test_calcular_promedio():
    resultado = calcular_promedio([8, 6, 10])

    assert resultado == 7

Al ejecutar python -m pytest, verás una salida similar:

E       assert 8.0 == 7

La línea indica que la expresión falló porque el valor obtenido fue 8.0 y el esperado era 7. Después de observar la falla, vuelve a dejar el valor correcto.

13.15 Leer el reporte de falla

Cuando una prueba falla, pytest muestra información en varias partes:

Parte del reporte Qué indica
Nombre de la prueba Qué función de prueba falló.
Línea del archivo En qué línea ocurrió la aserción fallida.
Expresión del assert Qué comparación no se cumplió.
Valores obtenidos Qué datos reales participaron en la comparación.

13.16 Mensajes personalizados en assert

Python permite agregar un mensaje al assert:

def test_promedio_esperado_con_mensaje():
    resultado = calcular_promedio([8, 6, 10])

    assert resultado == 8, "El promedio debe calcularse sumando valores y dividiendo por la cantidad"

No hace falta agregar mensajes a todas las pruebas. Úsalos cuando expliquen una regla de negocio o una intención que no sea obvia.

13.17 Archivo completo de pruebas

El archivo test_analisis.py puede quedar así:

import pytest

from analisis import (
    calcular_promedio,
    normalizar_etiqueta,
    obtener_aprobados,
    resumir_estudiante,
    validar_nota,
)


def test_calcular_promedio():
    resultado = calcular_promedio([8, 6, 10])
    assert resultado == 8


def test_promedio_de_lista_vacia_es_cero():
    resultado = calcular_promedio([])
    assert resultado == 0


def test_normalizar_etiqueta():
    resultado = normalizar_etiqueta("  Curso Python  ")
    assert resultado == "curso-python"


def test_estudiante_con_nota_6_esta_aprobado():
    estudiante = resumir_estudiante("Ana", 6)
    assert estudiante["aprobado"] is True


def test_estudiante_con_nota_5_no_esta_aprobado():
    estudiante = resumir_estudiante("Luis", 5)
    assert estudiante["aprobado"] is False


def test_obtener_aprobados():
    estudiantes = [
        {"nombre": "Ana", "nota": 8},
        {"nombre": "Luis", "nota": 5},
        {"nombre": "Marta", "nota": 7},
    ]
    resultado = obtener_aprobados(estudiantes)
    assert resultado == [
        {"nombre": "Ana", "nota": 8},
        {"nombre": "Marta", "nota": 7},
    ]


def test_resumir_estudiante():
    resultado = resumir_estudiante("  ana  ", 9)
    assert resultado == {
        "nombre": "Ana",
        "nota": 9,
        "aprobado": True,
    }


def test_nombre_normalizado_contiene_guion():
    resultado = normalizar_etiqueta("Curso Python")
    assert "-" in resultado


def test_nota_mayor_a_10_lanza_error():
    with pytest.raises(ValueError):
        validar_nota(11)


def test_nota_invalida_muestra_mensaje_claro():
    with pytest.raises(ValueError) as error:
        validar_nota(-1)

    assert str(error.value) == "La nota debe estar entre 0 y 10"

13.18 Ejecutar todas las pruebas

Ejecuta:

python -m pytest

La salida esperada será similar a:

collected 10 items

test_analisis.py ..........                                      [100%]

10 passed in 0.03s

13.19 Ejecutar con más detalle

Para ver nombres de pruebas y un reporte más explícito:

python -m pytest -v

Cuando estás leyendo fallas, la salida detallada ayuda a ubicar el caso exacto más rápido.

13.20 Errores frecuentes

  • Usar assert resultado cuando querías comparar un valor exacto: puede pasar con valores verdaderos pero incorrectos.
  • Comparar números decimales sin pensar en precisión: algunos cálculos pueden necesitar tolerancia.
  • Agregar mensajes personalizados a todo: puede generar ruido si no aportan información.
  • No leer la línea exacta del reporte: pytest indica dónde falló la prueba.
  • Dejar una falla intencional en el archivo: después de practicar, vuelve a dejar la expectativa correcta.

13.21 Comandos usados en este tema

mkdir pytest-assert-demo
cd pytest-assert-demo
python -m pip install pytest
python -m pytest
python -m pytest -v
python -m pytest test_analisis.py

13.22 Qué debes recordar de este tema

  • pytest usa assert para expresar comprobaciones.
  • Podemos comparar números, cadenas, booleanos, listas y diccionarios.
  • pytest.raises permite verificar excepciones esperadas.
  • El reporte de falla muestra la expresión y los valores involucrados.
  • Los mensajes personalizados deben usarse cuando agregan claridad real.
  • Leer bien una falla es parte del trabajo de testing.

13.23 Conclusión

En este tema profundizamos en las aserciones con assert y en la lectura de fallas de pytest. Vimos cómo comparar distintos tipos de datos y cómo interpretar el reporte cuando una expectativa no se cumple.

En el próximo tema aprenderemos a ejecutar una prueba, un archivo, una carpeta o toda la suite para trabajar con mayor precisión.