7. Anatomía de una prueba: preparación, ejecución y verificación

7.1 Introducción

Una prueba unitaria no debería ser un bloque de código desordenado. Para que sea fácil de leer y mantener, conviene reconocer sus partes principales: preparación, ejecución y verificación.

Esta estructura ayuda a que la intención de la prueba sea clara. Primero construimos el contexto necesario, luego ejecutamos la unidad que queremos probar y finalmente comprobamos el resultado esperado.

En este tema estudiaremos cada parte con ejemplos simples y veremos errores frecuentes que dificultan entender qué está probando una prueba.

7.2 Las tres partes esenciales

Una prueba unitaria típica puede dividirse en tres momentos:

Parte Pregunta que responde Ejemplo
Preparación ¿Qué datos y contexto necesita la prueba? Crear una cuenta con saldo inicial 100.
Ejecución ¿Qué acción estamos probando? Depositar 50 en la cuenta.
Verificación ¿Qué resultado esperamos observar? Comprobar que el saldo final sea 150.

Esta división no es una regla burocrática. Es una forma de escribir pruebas que comuniquen mejor su intención.

7.3 Primer ejemplo completo

Veamos una prueba sobre una clase Cuenta:

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

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


def test_depositar_incrementa_el_saldo():
    cuenta = Cuenta(100)

    cuenta.depositar(50)

    assert cuenta.saldo == 150

La prueba tiene una estructura clara:

  • Preparación: se crea una cuenta con saldo inicial 100.
  • Ejecución: se deposita 50.
  • Verificación: se comprueba que el saldo final sea 150.

7.4 Preparación

La preparación consiste en crear todo lo necesario para ejecutar la prueba. Puede incluir datos de entrada, objetos, configuraciones simples o dependencias controladas.

Ejemplos de preparación:

  • Crear una lista de productos.
  • Construir una cuenta con saldo inicial.
  • Definir una edad, un monto o una fecha.
  • Crear un objeto que represente un usuario.
  • Configurar una dependencia falsa o controlada.

Una buena preparación debe ser lo más pequeña posible. Si necesitamos demasiados datos para probar una regla simple, puede ser señal de que la unidad está muy acoplada o tiene demasiadas responsabilidades.

7.5 Ejemplo de preparación simple

Si queremos probar el total de un carrito, necesitamos preparar los ítems que formarán parte del cálculo.

def calcular_total(items):
    return sum(items)


def test_calcular_total_de_tres_items():
    items = [100, 200, 50]

    resultado = calcular_total(items)

    assert resultado == 350

La preparación es la lista items. No necesitamos crear una base de datos, una pantalla ni un usuario si la unidad solo calcula el total de una lista.

7.6 Preparación excesiva

La preparación excesiva vuelve las pruebas difíciles de leer. Si para probar una suma necesitamos construir diez objetos no relacionados, la prueba pierde claridad.

Ejemplo poco recomendable:

def test_calcular_total():
    usuario = Usuario("Ana", "ana@example.com", "cliente")
    direccion = Direccion("Calle 1", "Cordoba", "Argentina")
    sesion = Sesion(usuario)
    configuracion = Configuracion(moneda="ARS")
    items = [100, 200, 50]

    resultado = calcular_total(items)

    assert resultado == 350

Si calcular_total solo usa items, el resto de la preparación no aporta nada. Esa información distrae y puede confundir a quien lee la prueba.

7.7 Ejecución

La ejecución es el momento en que llamamos a la unidad que queremos probar. Idealmente, la prueba debería tener una acción principal clara.

En muchos casos, esa acción es una sola línea:

resultado = calcular_total(items)

En otros casos, puede ser una operación sobre un objeto:

cuenta.depositar(50)

Lo importante es que podamos identificar qué comportamiento estamos ejerciendo. Si la prueba ejecuta muchas acciones no relacionadas, será difícil entender qué causa una falla.

7.8 Una acción principal por prueba

Una prueba puede necesitar varias líneas de preparación, pero conviene que tenga una acción principal. Esto ayuda a diagnosticar fallas.

Ejemplo poco claro:

def test_cuenta():
    cuenta = Cuenta(100)
    cuenta.depositar(50)
    cuenta.extraer(30)
    cuenta.depositar(20)

    assert cuenta.saldo == 140

Esta prueba mezcla varias acciones. Si falla, debemos revisar más pasos para entender el problema.

Una alternativa más clara sería separar comportamientos:

def test_depositar_incrementa_el_saldo():
    cuenta = Cuenta(100)

    cuenta.depositar(50)

    assert cuenta.saldo == 150


def test_extraer_disminuye_el_saldo():
    cuenta = Cuenta(100)

    cuenta.extraer(30)

    assert cuenta.saldo == 70

7.9 Verificación

La verificación es la parte donde comprobamos si el resultado obtenido coincide con el resultado esperado. Sin verificación, no tenemos una prueba real, solo una ejecución de código.

La forma más común de verificar es mediante aserciones:

assert resultado == 350

La aserción debe expresar la expectativa central de la prueba. Si la expectativa no se cumple, la prueba falla.

Una prueba sin verificación puede pasar aunque el comportamiento sea incorrecto.

7.10 Verificar valores de retorno

El caso más simple es verificar el valor que devuelve una función.

def convertir_a_mayusculas(texto):
    return texto.upper()


def test_convertir_a_mayusculas():
    resultado = convertir_a_mayusculas("hola")

    assert resultado == "HOLA"

La prueba es clara porque la unidad recibe una entrada y devuelve una salida observable.

7.11 Verificar cambios de estado

No todas las unidades devuelven un valor. Algunas modifican el estado de un objeto. En ese caso, la verificación se realiza observando el estado después de ejecutar la acción.

class Carrito:
    def __init__(self):
        self.items = []

    def agregar_item(self, item):
        self.items.append(item)


def test_agregar_item_lo_incorpora_al_carrito():
    carrito = Carrito()

    carrito.agregar_item("teclado")

    assert carrito.items == ["teclado"]

La prueba no verifica un retorno, sino el estado final del carrito.

7.12 Verificar errores esperados

A veces el comportamiento correcto es rechazar una operación inválida. En esos casos, la prueba debe comprobar que se produzca el error esperado.

def dividir(a, b):
    if b == 0:
        raise ValueError("No se puede dividir por cero")
    return a / b


def test_dividir_por_cero_lanza_error():
    try:
        dividir(10, 0)
        assert False
    except ValueError:
        assert True

Muchos frameworks ofrecen formas más expresivas para verificar excepciones. Lo importante por ahora es entender la intención: una entrada inválida debe producir un error controlado.

7.13 Orden visual de la prueba

Separar visualmente las partes de la prueba mejora la lectura. Una práctica simple es dejar una línea en blanco entre preparación, ejecución y verificación.

def test_calcular_precio_final():
    precio = 1000
    descuento = 10

    resultado = calcular_precio_final(precio, descuento)

    assert resultado == 900

La línea en blanco ayuda a reconocer las partes sin necesidad de comentarios. En pruebas más complejas, también pueden usarse comentarios breves si aportan claridad.

7.14 Comentarios dentro de una prueba

Los comentarios pueden ayudar, pero no deberían compensar una prueba confusa. Si necesitamos muchos comentarios para explicar una prueba, quizá conviene mejorar nombres, separar casos o simplificar la preparación.

Comentarios útiles:

  • Marcar una preparación poco obvia.
  • Explicar una regla de negocio extraña.
  • Aclarar una decisión temporal o una limitación conocida.

Comentarios poco útiles:

  • Repetir literalmente lo que dice el código.
  • Explicar nombres poco claros en lugar de corregirlos.
  • Ocultar que la prueba mezcla demasiadas responsabilidades.

7.15 Nombres que ayudan a leer la anatomía

Los nombres de variables también ayudan a distinguir las partes. Conviene usar nombres que expresen el rol de cada dato.

def test_aplicar_descuento_del_10_por_ciento():
    precio_original = 1000
    porcentaje_descuento = 10

    precio_final = aplicar_descuento(precio_original, porcentaje_descuento)

    assert precio_final == 900

Los nombres precio_original, porcentaje_descuento y precio_final comunican mejor la intención que nombres genéricos como x, y o r.

7.16 Errores frecuentes en la preparación

Algunos problemas comunes son:

  • Preparar datos que la prueba no usa.
  • Crear objetos complejos para verificar una regla simple.
  • Depender de datos externos innecesarios.
  • Ocultar información importante en configuraciones difíciles de encontrar.
  • Repetir mucho código de preparación sin necesidad.

Una preparación clara debe permitir entender rápidamente en qué condiciones se ejecuta la unidad.

7.17 Errores frecuentes en la ejecución

En la etapa de ejecución, los errores más habituales son:

  • Ejecutar varias acciones principales en una sola prueba.
  • Modificar el estado después de haber hecho la verificación.
  • Llamar indirectamente a la unidad mediante demasiadas capas.
  • No dejar claro cuál es la acción que realmente se está probando.

Si no podemos señalar la línea principal de ejecución, la prueba probablemente está mezclando demasiadas ideas.

7.18 Errores frecuentes en la verificación

En la verificación, los problemas más comunes son:

  • No tener ninguna aserción.
  • Verificar algo distinto al objetivo de la prueba.
  • Usar una expectativa demasiado general.
  • Comprobar detalles internos que no deberían importar.
  • Incluir muchas aserciones no relacionadas.

La verificación debe responder con precisión: ¿qué resultado demuestra que la unidad se comportó correctamente?

7.19 Tabla de diagnóstico

Problema observado Parte a revisar Posible mejora
La prueba es difícil de entender antes de la acción. Preparación Eliminar datos innecesarios o crear helpers claros.
No queda claro qué se está probando. Ejecución Dejar una acción principal por prueba.
La prueba pasa aunque el resultado sea incorrecto. Verificación Agregar una aserción con resultado esperado.
La prueba falla por detalles internos. Verificación Verificar comportamiento observable.

7.20 Qué debes recordar de este tema

  • Una prueba unitaria suele tener preparación, ejecución y verificación.
  • La preparación crea los datos y el contexto necesarios.
  • La ejecución llama a la unidad o realiza la acción principal.
  • La verificación comprueba el resultado esperado mediante aserciones.
  • Una prueba sin verificación no comprueba comportamiento.
  • Conviene evitar preparación excesiva y acciones múltiples no relacionadas.
  • Una estructura clara facilita leer, mantener y diagnosticar pruebas.

7.21 Conclusión

La anatomía de una prueba unitaria nos ayuda a escribir pruebas más claras. Preparar, ejecutar y verificar son pasos simples, pero ordenarlos correctamente mejora mucho la legibilidad.

Una prueba bien estructurada deja claro qué contexto se creó, qué acción se probó y qué resultado se esperaba. Cuando falla, esa claridad ayuda a encontrar la causa con menos esfuerzo.

En el próximo tema veremos el patrón Arrange, Act, Assert, una forma muy conocida de nombrar y aplicar esta misma estructura.