10. Aserciones: comprobar resultados esperados

10.1 Introducción

Una prueba unitaria necesita comprobar algo. Esa comprobación se realiza mediante una aserción. La aserción compara lo que el código produjo con lo que esperábamos obtener.

Sin aserciones, una prueba puede ejecutar código pero no verificar su comportamiento. Por eso decimos que las aserciones son el corazón de una prueba automatizada.

En este tema veremos qué son, cómo se escriben, qué tipos de resultados pueden comprobar y qué errores conviene evitar.

10.2 Qué es una aserción

Una aserción es una afirmación que la prueba espera que sea verdadera. Si la afirmación se cumple, la prueba continúa o pasa. Si no se cumple, la prueba falla.

def sumar(a, b):
    return a + b


def test_sumar_dos_numeros():
    resultado = sumar(2, 3)

    assert resultado == 5

La línea assert resultado == 5 afirma que el resultado esperado es 5. Si sumar(2, 3) devolviera otro valor, la prueba fallaría.

10.3 Resultado esperado y resultado obtenido

En toda aserción hay dos ideas:

  • Resultado obtenido: lo que devolvió o produjo la unidad probada.
  • Resultado esperado: lo que la prueba considera correcto.

Ejemplo:

resultado_obtenido = aplicar_descuento(1000, 10)
resultado_esperado = 900

assert resultado_obtenido == resultado_esperado

Separar estas ideas ayuda a razonar sobre la prueba y a detectar si el error está en el código o en la expectativa mal escrita.

10.4 Comparar valores simples

El caso más común es comparar números, textos o valores booleanos.

def test_calcular_iva():
    assert calcular_iva(1000) == 210


def test_normalizar_nombre():
    assert normalizar_nombre(" ana ") == "Ana"


def test_edad_18_es_mayor_de_edad():
    assert es_mayor_de_edad(18) == True

Estas aserciones son simples y directas. La prueba expresa exactamente qué salida esperamos para una entrada concreta.

10.5 Aserciones booleanas

Cuando una función devuelve True o False, podemos comprobar el valor esperado.

def password_es_valido(password):
    return len(password) >= 8


def test_password_de_8_caracteres_es_valido():
    assert password_es_valido("abcdefgh") == True


def test_password_de_7_caracteres_no_es_valido():
    assert password_es_valido("abcdefg") == False

Algunos frameworks permiten aserciones más expresivas, como assertTrue o assertFalse. Lo importante es que la intención sea clara.

10.6 Aserciones sobre colecciones

Muchas unidades trabajan con listas, conjuntos o diccionarios. En esos casos podemos verificar contenido, cantidad u orden.

def obtener_pares(numeros):
    return [numero for numero in numeros if numero % 2 == 0]


def test_obtener_pares_devuelve_solo_numeros_pares():
    resultado = obtener_pares([1, 2, 3, 4, 5, 6])

    assert resultado == [2, 4, 6]

Esta aserción verifica tanto el contenido como el orden de la lista. Si el orden no importa, conviene usar una verificación que lo refleje.

10.7 Cuando el orden no importa

Si el comportamiento esperado no depende del orden, la aserción debería evitar exigir un orden innecesario.

def test_obtener_roles_del_usuario():
    roles = obtener_roles("ana")

    assert set(roles) == {"admin", "editor"}

Con esta aserción, la prueba no falla si los roles llegan como ["editor", "admin"]. Solo verifica que el conjunto de roles sea el esperado.

10.8 Aserciones sobre longitud

A veces no necesitamos verificar todos los elementos, sino la cantidad de resultados.

def test_filtrar_productos_activos_devuelve_dos_productos():
    productos = [
        {"nombre": "A", "activo": True},
        {"nombre": "B", "activo": False},
        {"nombre": "C", "activo": True}
    ]

    activos = filtrar_productos_activos(productos)

    assert len(activos) == 2

Esta prueba verifica cantidad. Si además importa cuáles son los productos, conviene agregar una aserción sobre el contenido.

10.9 Aserciones sobre objetos

Cuando una unidad devuelve un objeto, podemos verificar propiedades relevantes.

def test_crear_usuario_asigna_datos_basicos():
    usuario = crear_usuario("Ana", "ana@example.com")

    assert usuario.nombre == "Ana"
    assert usuario.email == "ana@example.com"

Estas dos aserciones están relacionadas: ambas verifican que el usuario fue creado con sus datos básicos. No conviene agregar aserciones no relacionadas solo porque el objeto tiene muchas propiedades.

10.10 Aserciones sobre cambios de estado

Si la unidad modifica un objeto, la aserción debe mirar el estado final relevante.

class Cuenta:
    def __init__(self, saldo):
        self.saldo = saldo

    def depositar(self, importe):
        self.saldo += importe


def test_depositar_incrementa_saldo():
    cuenta = Cuenta(100)

    cuenta.depositar(50)

    assert cuenta.saldo == 150

La aserción no verifica que el método fue llamado; verifica el efecto observable de la operación.

10.11 Aserciones sobre errores esperados

Cuando una entrada inválida debe producir un error, la prueba debe verificar ese error.

def retirar(saldo, importe):
    if importe > saldo:
        raise ValueError("Saldo insuficiente")
    return saldo - importe


def test_retirar_mas_del_saldo_lanza_error():
    try:
        retirar(100, 150)
        assert False
    except ValueError:
        assert True

El patrón puede variar según el framework, pero la intención es la misma: comprobar que el caso inválido no se acepta silenciosamente.

10.12 Aserciones con números decimales

Con números decimales puede haber diferencias pequeñas por la forma en que las computadoras representan valores flotantes. En esos casos, no siempre conviene comparar igualdad exacta.

def test_calcular_promedio():
    resultado = calcular_promedio([0.1, 0.2])

    assert abs(resultado - 0.15) < 0.0001

La aserción verifica que el resultado esté suficientemente cerca del valor esperado. Muchos frameworks ofrecen métodos específicos para comparar valores aproximados.

10.13 Mensajes de falla

Algunas herramientas permiten agregar mensajes a las aserciones. Un mensaje puede ayudar cuando la expectativa no es obvia.

def test_descuento_cliente_vip():
    descuento = calcular_descuento("vip", 1000)

    assert descuento == 150, "Un cliente VIP debe recibir 15% de descuento"

No es necesario agregar mensajes a todas las aserciones. Si el nombre de la prueba y la aserción son claros, el mensaje puede ser redundante.

10.14 Aserciones demasiado generales

Una aserción general puede pasar aunque el comportamiento no sea completamente correcto.

def test_calcular_descuento():
    descuento = calcular_descuento("vip", 1000)

    assert descuento > 0

Esta prueba solo verifica que haya algún descuento positivo. Si el descuento correcto es 150, pero la función devuelve 1, la prueba pasa igual. Una mejor aserción sería:

def test_calcular_descuento_cliente_vip():
    descuento = calcular_descuento("vip", 1000)

    assert descuento == 150

10.15 Aserciones que repiten la implementación

Un error común es calcular el resultado esperado usando la misma lógica que estamos probando. Eso reduce el valor de la prueba.

def test_aplicar_descuento():
    precio = 1000
    porcentaje = 10

    resultado = aplicar_descuento(precio, porcentaje)

    assert resultado == precio - (precio * porcentaje / 100)

La expectativa repite la fórmula. Si la fórmula conceptual estuviera mal entendida, la prueba podría repetir el mismo error. Para este caso, es mejor usar un valor esperado concreto:

assert resultado == 900

10.16 Aserciones sin valor real

Algunas aserciones no aportan información útil.

def test_usuario():
    usuario = crear_usuario("Ana")

    assert usuario is not None

Esta aserción puede ser insuficiente si lo importante es que el usuario tenga nombre, estado inicial o permisos correctos. Una versión más útil sería:

def test_crear_usuario_asigna_nombre():
    usuario = crear_usuario("Ana")

    assert usuario.nombre == "Ana"

10.17 Varias aserciones relacionadas

Una prueba puede tener varias aserciones si todas forman parte del mismo comportamiento.

def test_crear_producto_asigna_datos_iniciales():
    producto = crear_producto("Teclado", 1000)

    assert producto.nombre == "Teclado"
    assert producto.precio == 1000
    assert producto.activo == True

Las tres aserciones verifican la creación inicial del producto. Si en cambio agregáramos aserciones sobre descuentos, stock y permisos, probablemente estaríamos mezclando objetivos distintos.

10.18 Tabla de tipos de aserción

Qué queremos verificar Ejemplo de aserción
Valor numérico assert total == 150
Texto assert nombre == "Ana"
Booleano assert es_valido == True
Lista con orden assert resultado == [1, 2, 3]
Conjunto sin orden assert set(roles) == {"admin", "editor"}
Objeto assert usuario.email == "ana@example.com"
Número aproximado assert abs(resultado - esperado) < 0.0001

10.19 Lista de comprobación

Antes de dejar una aserción, conviene revisar:

  • ¿Comprueba el comportamiento principal de la prueba?
  • ¿Compara contra un resultado esperado claro?
  • ¿Es suficientemente específica?
  • ¿Evita repetir la implementación?
  • ¿No exige detalles irrelevantes?
  • ¿Ayudará a diagnosticar la falla?

Una aserción clara convierte la prueba en una verificación útil.

10.20 Qué debes recordar de este tema

  • Una aserción comprueba que una expectativa sea verdadera.
  • Sin aserción, una prueba puede ejecutar código sin verificar comportamiento.
  • Las aserciones pueden verificar valores, colecciones, objetos, estado y errores esperados.
  • La aserción debe ser específica y estar relacionada con el objetivo de la prueba.
  • Conviene evitar aserciones demasiado generales.
  • No es buena idea repetir la implementación para calcular el resultado esperado.
  • Una buena aserción ayuda a diagnosticar qué comportamiento falló.

10.21 Conclusión

Las aserciones son la parte de la prueba que decide si el comportamiento observado coincide con el comportamiento esperado. Por eso deben escribirse con intención y claridad.

Una buena aserción no solo hace fallar la prueba cuando algo está mal; también explica qué expectativa no se cumplió.

En el próximo tema estudiaremos las pruebas sobre valores de retorno, uno de los casos más habituales al comenzar con pruebas unitarias.