8. El patrón Arrange, Act, Assert

8.1 Introducción

En el tema anterior vimos que una prueba unitaria suele tener tres partes: preparación, ejecución y verificación. El patrón Arrange, Act, Assert, también conocido como AAA, es una forma muy usada de nombrar y ordenar esas partes.

Este patrón no pertenece a un lenguaje específico. Puede aplicarse en Python, Java, JavaScript, C#, PHP y muchos otros entornos. Su objetivo es que las pruebas sean más fáciles de leer, entender y diagnosticar.

Cuando una prueba sigue AAA, quien la lee puede distinguir rápidamente qué datos se preparan, qué acción se prueba y qué resultado se espera.

8.2 Significado de Arrange, Act, Assert

Parte Traducción práctica Qué hacemos
Arrange Preparar Crear datos, objetos y contexto necesario.
Act Actuar o ejecutar Llamar a la unidad o realizar la acción principal.
Assert Verificar Comprobar que el resultado obtenido coincide con el esperado.

En español podríamos decir Preparar, Ejecutar y Verificar. Aun así, es muy habitual encontrar los nombres en inglés en documentación, cursos y equipos de desarrollo.

8.3 Ejemplo básico

Veamos una prueba simple con las tres partes separadas visualmente:

def aplicar_descuento(precio, porcentaje):
    return precio - (precio * porcentaje / 100)


def test_aplicar_descuento_del_10_por_ciento():
    precio = 1000
    porcentaje = 10

    resultado = aplicar_descuento(precio, porcentaje)

    assert resultado == 900

La estructura es directa:

  • Arrange: se definen precio y porcentaje.
  • Act: se llama a aplicar_descuento.
  • Assert: se verifica que el resultado sea 900.

8.4 Usar comentarios AAA

En algunas pruebas, especialmente al comenzar, puede ser útil escribir comentarios para marcar las tres partes.

def test_aplicar_descuento_del_10_por_ciento():
    # Arrange
    precio = 1000
    porcentaje = 10

    # Act
    resultado = aplicar_descuento(precio, porcentaje)

    # Assert
    assert resultado == 900

Estos comentarios pueden ayudar a aprender el patrón. Sin embargo, cuando la prueba es simple, las líneas en blanco y los nombres claros suelen ser suficientes.

8.5 Arrange: preparar solo lo necesario

La sección Arrange debe contener los datos y objetos indispensables para ejecutar la prueba. Una preparación excesiva hace que el caso sea más difícil de entender.

Ejemplo correcto:

def test_total_de_carrito_con_dos_items():
    carrito = Carrito()
    carrito.agregar_item(100)
    carrito.agregar_item(50)

    total = carrito.calcular_total()

    assert total == 150

La preparación crea un carrito y agrega los ítems necesarios. No hay datos ajenos al comportamiento que se quiere verificar.

8.6 Arrange excesivo

Comparemos con una preparación que agrega ruido:

def test_total_de_carrito_con_dos_items():
    usuario = Usuario("Ana", "ana@example.com")
    direccion = Direccion("Calle 1", "Cordoba")
    metodo_pago = MetodoPago("tarjeta")
    carrito = Carrito()
    carrito.asignar_usuario(usuario)
    carrito.asignar_direccion(direccion)
    carrito.asignar_metodo_pago(metodo_pago)
    carrito.agregar_item(100)
    carrito.agregar_item(50)

    total = carrito.calcular_total()

    assert total == 150

Si usuario, dirección y método de pago no influyen en el cálculo del total, esta preparación distrae. Además, aumenta el riesgo de que la prueba falle por datos que no forman parte del objetivo.

8.7 Act: una acción principal

La sección Act debería contener la acción que queremos probar. En una prueba unitaria clara suele haber una acción principal.

resultado = calcular_total(items)

Cuando hay muchas acciones principales, la prueba puede estar verificando demasiadas cosas.

def test_operaciones_de_cuenta():
    cuenta = Cuenta(100)

    cuenta.depositar(50)
    cuenta.extraer(20)
    cuenta.depositar(10)

    assert cuenta.saldo == 140

Esta prueba puede tener sentido como escenario, pero no es la forma más clara de probar unitariamente cada operación. Es mejor separar casos si queremos verificar depósitos y extracciones de manera precisa.

8.8 Assert: verificar la intención central

La sección Assert expresa el resultado esperado. Una prueba debe fallar si el comportamiento importante no se cumple.

def test_cliente_vip_tiene_descuento():
    cliente = Cliente(tipo="vip")
    compra = Compra(total=1000)

    descuento = calcular_descuento(cliente, compra)

    assert descuento == 150

La aserción no debe ser decorativa. Debe comprobar exactamente aquello que justifica la existencia de la prueba.

8.9 Más de una aserción

Una duda común es si una prueba puede tener más de una aserción. La respuesta práctica es: sí, cuando las aserciones verifican la misma idea o el mismo resultado compuesto.

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

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

Las dos aserciones verifican el mismo objetivo: que el usuario se creó con sus datos básicos. En cambio, si las aserciones mezclan creación, permisos, descuentos y notificaciones, probablemente convenga separar pruebas.

8.10 AAA con errores esperados

El patrón AAA también sirve para pruebas donde esperamos un error controlado.

def validar_edad(edad):
    if edad < 0:
        raise ValueError("La edad no puede ser negativa")
    return True


def test_edad_negativa_lanza_error():
    edad = -1

    try:
        validar_edad(edad)
        assert False
    except ValueError:
        assert True

La preparación define la edad inválida. La acción intenta validar. La verificación comprueba que se produzca el error esperado. En frameworks reales existen formas más expresivas para este caso, pero la estructura conceptual es la misma.

8.11 AAA con cambios de estado

Cuando la unidad modifica un objeto, Arrange prepara el estado inicial, Act ejecuta la operación y Assert verifica el estado final.

class Contador:
    def __init__(self):
        self.valor = 0

    def incrementar(self):
        self.valor += 1


def test_incrementar_aumenta_el_valor_en_uno():
    contador = Contador()

    contador.incrementar()

    assert contador.valor == 1

La prueba es pequeña, pero muestra claramente el antes, la acción y el después.

8.12 AAA con funciones puras

Las funciones puras suelen encajar muy bien con AAA porque reciben entradas y devuelven salidas sin modificar estado externo.

def normalizar_nombre(nombre):
    return nombre.strip().title()


def test_normalizar_nombre_elimina_espacios_y_capitaliza():
    nombre = "  ana perez  "

    resultado = normalizar_nombre(nombre)

    assert resultado == "Ana Perez"

El patrón queda muy limpio: un dato de entrada, una llamada y una salida esperada.

8.13 Evitar mezclar Arrange con Act

A veces una prueba oculta la acción principal dentro de la preparación. Esto dificulta entender qué se está probando.

def test_total():
    total = calcular_total([100, 50])

    assert total == 150

Esta prueba es tan simple que puede aceptarse, pero en casos más grandes conviene separar datos de acción:

def test_total():
    items = [100, 50]

    total = calcular_total(items)

    assert total == 150

La segunda versión muestra con más claridad qué se preparó y qué se ejecutó.

8.14 Evitar mezclar Act con Assert

También es común llamar a la unidad directamente dentro de la aserción:

def test_total():
    items = [100, 50]

    assert calcular_total(items) == 150

En pruebas simples esto es aceptable. Sin embargo, cuando la acción es importante o el resultado se usa en varias verificaciones, suele ser más legible guardar el resultado en una variable:

def test_total():
    items = [100, 50]

    total = calcular_total(items)

    assert total == 150

8.15 Cuándo no hace falta ser rígido

AAA es una guía, no una ley. En pruebas muy simples, separar todo puede resultar innecesario.

def test_sumar_dos_numeros():
    assert sumar(2, 3) == 5

Esta prueba es comprensible aunque no tenga tres bloques visibles. La pregunta importante es si la prueba comunica bien su intención. Si la respuesta es sí, no necesitamos agregar estructura artificial.

8.16 AAA y legibilidad

El valor de AAA está en la legibilidad. Una prueba debería poder leerse casi como una pequeña historia técnica:

  • Dado este contexto...
  • Cuando ejecuto esta acción...
  • Entonces espero este resultado...

Si una prueba no permite identificar esos tres momentos, probablemente necesita simplificarse o dividirse.

8.17 AAA y diagnóstico de fallas

Cuando una prueba AAA falla, es más fácil ubicar el problema. Podemos revisar:

  • Si el contexto preparado era correcto.
  • Si la acción ejecutada fue la que queríamos probar.
  • Si la expectativa era correcta y estaba bien expresada.

Una prueba desordenada obliga a leer todo el bloque para descubrir qué parte corresponde a cada rol. Eso aumenta el costo de mantenimiento.

8.18 Tabla de errores comunes

Error Parte afectada Mejora posible
Crear datos que no se usan. Arrange Eliminar preparación innecesaria.
Ejecutar varias acciones principales. Act Separar pruebas por comportamiento.
No comprobar el resultado. Assert Agregar una aserción significativa.
Verificar detalles internos. Assert Comprobar comportamiento observable.
Usar nombres genéricos. Toda la prueba Nombrar datos, acción y resultado con intención.

8.19 Lista de comprobación

Antes de dejar una prueba, podemos revisar:

  • ¿Se entiende qué datos o contexto prepara?
  • ¿Hay una acción principal identificable?
  • ¿La aserción verifica el comportamiento importante?
  • ¿La prueba evita datos innecesarios?
  • ¿El nombre de la prueba coincide con lo que verifica?
  • ¿Si falla, el mensaje o la ubicación ayudarán a diagnosticar?

Si la respuesta es negativa en varios puntos, conviene revisar la estructura.

8.20 Qué debes recordar de este tema

  • Arrange, Act, Assert significa preparar, ejecutar y verificar.
  • Arrange crea los datos y el contexto necesario.
  • Act realiza la acción principal que queremos probar.
  • Assert comprueba el resultado esperado.
  • El patrón mejora la lectura y el diagnóstico de fallas.
  • No hace falta aplicarlo con rigidez artificial en pruebas muy simples.
  • Una prueba AAA debe comunicar claramente su intención.

8.21 Conclusión

El patrón Arrange, Act, Assert es una herramienta simple para ordenar pruebas unitarias. Su mayor beneficio es que obliga a separar contexto, acción y resultado esperado.

Una prueba bien organizada no solo verifica código: también comunica mejor qué comportamiento importa y facilita encontrar la causa cuando algo falla.

En el próximo tema estudiaremos cómo elegir nombres claros para las pruebas, un aspecto fundamental para que una suite sea fácil de leer y mantener.