6. Aserciones principales de unittest

6.1 Objetivo del tema

Una aserción es la comprobación que decide si una prueba pasa o falla. En unittest, las aserciones se escriben mediante métodos de unittest.TestCase, como assertEqual, assertTrue o assertRaises.

En este tema veremos las aserciones más usadas, cuándo conviene usar cada una y cómo leer mejor la intención de una prueba.

Idea clave: una prueba sin aserción normalmente ejecuta código, pero no verifica un comportamiento esperado.

6.2 Crear una carpeta de práctica

Crea un proyecto nuevo:

mkdir aserciones-unittest-demo
cd aserciones-unittest-demo

En este ejemplo no instalaremos paquetes, porque unittest ya viene incluido con Python.

6.3 Crear el código a probar

Crea un archivo llamado usuarios.py:

def crear_usuario(nombre, edad, email=None):
    if not nombre or not nombre.strip():
        raise ValueError("El nombre es obligatorio")
    if edad < 0:
        raise ValueError("La edad no puede ser negativa")

    return {
        "nombre": nombre.strip().title(),
        "edad": edad,
        "email": email,
        "activo": edad >= 18,
        "roles": ["usuario"],
    }


def obtener_dominio(email):
    if "@" not in email:
        raise ValueError("Email inválido")
    return email.split("@")[1].lower()


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

Este archivo nos permite probar diccionarios, listas, valores booleanos, None, excepciones, cadenas y números decimales.

6.4 Crear el archivo de pruebas

Crea test_usuarios.py:

import unittest

from usuarios import calcular_promedio, crear_usuario, obtener_dominio


class TestUsuarios(unittest.TestCase):
    pass

Agregaremos los métodos de prueba dentro de esta clase.

6.5 assertEqual

assertEqual comprueba que dos valores sean iguales. Es una de las aserciones más usadas.

def test_crear_usuario_normaliza_nombre(self):
    usuario = crear_usuario("  ana  ", 20)

    self.assertEqual(usuario["nombre"], "Ana")

Si el nombre obtenido no es "Ana", la prueba falla y muestra la diferencia.

6.6 assertNotEqual

assertNotEqual comprueba que dos valores sean diferentes.

def test_usuario_no_conserva_espacios_en_nombre(self):
    usuario = crear_usuario("  ana  ", 20)

    self.assertNotEqual(usuario["nombre"], "  ana  ")

Conviene usarla cuando la diferencia forma parte clara del comportamiento esperado.

6.7 assertTrue

assertTrue comprueba que una expresión sea verdadera.

def test_usuario_mayor_de_edad_esta_activo(self):
    usuario = crear_usuario("Ana", 18)

    self.assertTrue(usuario["activo"])

Es útil para valores booleanos o condiciones que deben cumplirse.

6.8 assertFalse

assertFalse comprueba que una expresión sea falsa.

def test_usuario_menor_de_edad_no_esta_activo(self):
    usuario = crear_usuario("Luis", 17)

    self.assertFalse(usuario["activo"])

En este caso, la regla indica que una persona menor de 18 años no queda activa.

6.9 assertIsNone

assertIsNone comprueba que un valor sea exactamente None.

def test_promedio_de_lista_vacia_es_none(self):
    resultado = calcular_promedio([])

    self.assertIsNone(resultado)

Es más expresivo que escribir assertEqual(resultado, None).

6.10 assertIsNotNone

assertIsNotNone comprueba que un valor no sea None.

def test_usuario_tiene_lista_de_roles(self):
    usuario = crear_usuario("Ana", 20)

    self.assertIsNotNone(usuario["roles"])

6.11 assertIn

assertIn comprueba que un valor esté dentro de una colección o cadena.

def test_usuario_tiene_rol_usuario(self):
    usuario = crear_usuario("Ana", 20)

    self.assertIn("usuario", usuario["roles"])

También puede usarse con cadenas:

def test_dominio_contiene_punto(self):
    dominio = obtener_dominio("ana@example.com")

    self.assertIn(".", dominio)

6.12 assertNotIn

assertNotIn comprueba que un valor no esté dentro de una colección o cadena.

def test_usuario_no_tiene_rol_admin_por_defecto(self):
    usuario = crear_usuario("Ana", 20)

    self.assertNotIn("admin", usuario["roles"])

6.13 assertRaises

assertRaises comprueba que una operación lance una excepción esperada.

def test_nombre_vacio_lanza_error(self):
    with self.assertRaises(ValueError):
        crear_usuario("   ", 20)

Esta forma con with es clara y permite encerrar solamente la línea que debe producir el error.

6.14 assertRaises con mensaje de error

A veces queremos comprobar también el mensaje de la excepción:

def test_email_invalido_lanza_mensaje_claro(self):
    with self.assertRaises(ValueError) as contexto:
        obtener_dominio("correo-invalido")

    self.assertEqual(str(contexto.exception), "Email inválido")

Esto debe usarse cuando el mensaje forma parte importante del comportamiento esperado.

6.15 assertAlmostEqual

Los números decimales pueden tener pequeñas diferencias por la forma en que las computadoras representan valores de punto flotante. Para estos casos existe assertAlmostEqual.

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

    self.assertAlmostEqual(resultado, 0.15)

Es útil para resultados con decimales, porcentajes, promedios y cálculos similares.

6.16 assertGreater y assertLess

assertGreater comprueba que un valor sea mayor que otro. assertLess comprueba que sea menor.

def test_edad_de_usuario_es_mayor_que_cero(self):
    usuario = crear_usuario("Ana", 20)

    self.assertGreater(usuario["edad"], 0)


def test_usuario_menor_tiene_edad_menor_a_18(self):
    usuario = crear_usuario("Luis", 17)

    self.assertLess(usuario["edad"], 18)

6.17 assertIsInstance

assertIsInstance comprueba que un valor sea instancia de un tipo determinado.

def test_crear_usuario_devuelve_diccionario(self):
    usuario = crear_usuario("Ana", 20)

    self.assertIsInstance(usuario, dict)

No conviene abusar de esta aserción. Es útil cuando el tipo devuelto forma parte del contrato que queremos garantizar.

6.18 Mensajes personalizados

La mayoría de las aserciones permiten agregar un mensaje final. Ese mensaje se muestra si la prueba falla.

def test_usuario_adulto_debe_quedar_activo(self):
    usuario = crear_usuario("Ana", 18)

    self.assertTrue(
        usuario["activo"],
        "Un usuario de 18 años o más debe quedar activo",
    )

Úsalos cuando ayuden a entender la regla de negocio. No hace falta agregar mensajes obvios a todas las aserciones.

6.19 Archivo completo de pruebas

El archivo test_usuarios.py puede quedar así:

import unittest

from usuarios import calcular_promedio, crear_usuario, obtener_dominio


class TestUsuarios(unittest.TestCase):

    def test_crear_usuario_normaliza_nombre(self):
        usuario = crear_usuario("  ana  ", 20)
        self.assertEqual(usuario["nombre"], "Ana")

    def test_usuario_mayor_de_edad_esta_activo(self):
        usuario = crear_usuario("Ana", 18)
        self.assertTrue(usuario["activo"])

    def test_usuario_menor_de_edad_no_esta_activo(self):
        usuario = crear_usuario("Luis", 17)
        self.assertFalse(usuario["activo"])

    def test_promedio_de_lista_vacia_es_none(self):
        resultado = calcular_promedio([])
        self.assertIsNone(resultado)

    def test_usuario_tiene_rol_usuario(self):
        usuario = crear_usuario("Ana", 20)
        self.assertIn("usuario", usuario["roles"])

    def test_usuario_no_tiene_rol_admin_por_defecto(self):
        usuario = crear_usuario("Ana", 20)
        self.assertNotIn("admin", usuario["roles"])

    def test_nombre_vacio_lanza_error(self):
        with self.assertRaises(ValueError):
            crear_usuario("   ", 20)

    def test_email_invalido_lanza_mensaje_claro(self):
        with self.assertRaises(ValueError) as contexto:
            obtener_dominio("correo-invalido")

        self.assertEqual(str(contexto.exception), "Email inválido")

    def test_promedio_con_decimales(self):
        resultado = calcular_promedio([0.1, 0.2])
        self.assertAlmostEqual(resultado, 0.15)

    def test_crear_usuario_devuelve_diccionario(self):
        usuario = crear_usuario("Ana", 20)
        self.assertIsInstance(usuario, dict)


if __name__ == "__main__":
    unittest.main()

6.20 Ejecutar las pruebas

Desde la carpeta del proyecto:

python -m unittest -v

La salida debe mostrar todas las pruebas con resultado ok.

6.21 Elegir la aserción correcta

Una buena aserción expresa la intención de la prueba. Por ejemplo, si esperamos None, es más claro usar assertIsNone que assertEqual(resultado, None).

Necesidad Aserción recomendada
Comparar dos valores assertEqual
Verificar verdadero o falso assertTrue o assertFalse
Verificar None assertIsNone
Verificar pertenencia assertIn o assertNotIn
Verificar una excepción assertRaises
Comparar decimales assertAlmostEqual

6.22 Errores frecuentes

  • Usar siempre assertEqual: muchas veces hay una aserción más expresiva.
  • No probar excepciones: los errores esperados también son comportamiento del programa.
  • Comparar decimales exactos: para cálculos con punto flotante conviene usar assertAlmostEqual.
  • Agregar demasiadas aserciones no relacionadas: una prueba debe tener una intención clara.
  • Escribir mensajes personalizados obvios: solo agregan ruido si no explican una regla importante.

6.23 Comandos usados en este tema

mkdir aserciones-unittest-demo
cd aserciones-unittest-demo
python -m unittest
python -m unittest -v
python -m unittest test_usuarios.TestUsuarios.test_nombre_vacio_lanza_error

6.24 Qué debes recordar de este tema

  • Las aserciones determinan si una prueba pasa o falla.
  • unittest ofrece muchas aserciones específicas.
  • Elegir una aserción expresiva mejora la lectura de la prueba.
  • assertRaises sirve para validar errores esperados.
  • assertAlmostEqual es útil para cálculos decimales.
  • Una prueba debe verificar una intención clara.

6.25 Conclusión

En este tema vimos las aserciones principales de unittest. Ya podemos comparar valores, verificar booleanos, comprobar pertenencia, validar None, probar excepciones y trabajar con números decimales.

En el próximo tema usaremos estas ideas para probar funciones puras y valores de retorno, uno de los casos más simples y frecuentes en Testing en Python.