18. Datos de prueba simples, claros y mantenibles

18.1 Introducción

Los datos de prueba son los valores, objetos y condiciones que usamos para ejecutar una prueba. Pueden ser números, textos, listas, fechas, usuarios, productos o cualquier estructura que la unidad necesite.

Elegir buenos datos de prueba es tan importante como escribir buenas aserciones. Datos confusos producen pruebas difíciles de leer; datos excesivos ocultan la intención; datos mal elegidos pueden hacer que la prueba no cubra el riesgo correcto.

En este tema veremos cómo preparar datos simples, claros y mantenibles para pruebas unitarias.

18.2 Qué hace bueno a un dato de prueba

Un buen dato de prueba cumple varias condiciones:

  • Está relacionado directamente con el comportamiento probado.
  • Es fácil de entender al leer la prueba.
  • No agrega información innecesaria.
  • Hace visible la regla o el límite que queremos verificar.
  • Puede mantenerse si cambia el código.
Los datos de prueba deben ayudar a leer la intención de la prueba, no esconderla.

18.3 Datos mínimos suficientes

Una buena regla es preparar solo los datos necesarios para el caso. Si una prueba verifica un cálculo de total, no necesita usuario, dirección, sesión y método de pago salvo que esos datos influyan en el cálculo.

def calcular_total(items):
    return sum(items)


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

    total = calcular_total(items)

    assert total == 350

Los datos son mínimos y suficientes: una lista de importes. No hay ruido adicional.

18.4 Preparación con ruido

Veamos una versión menos clara:

def test_calcular_total_de_tres_items():
    usuario = Usuario("Ana", "ana@example.com")
    direccion = Direccion("Calle 1", "Cordoba")
    sesion = Sesion(usuario)
    items = [100, 200, 50]

    total = calcular_total(items)

    assert total == 350

Si calcular_total no usa usuario, dirección ni sesión, esos datos distraen. Además, pueden introducir fallas que no tienen relación con el comportamiento probado.

18.5 Nombres expresivos para datos

Los nombres de variables deben explicar el rol del dato en la prueba.

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

Estos nombres son más claros que x, y o r. En una prueba, la legibilidad vale más que ahorrar algunos caracteres.

18.6 Evitar datos mágicos

Un dato mágico es un valor que aparece sin explicar por qué fue elegido.

def test_descuento():
    assert calcular_descuento(13742) == 2061.3

Esta prueba puede ser correcta, pero no queda claro por qué se eligió 13742. Si el objetivo es probar un descuento del 15%, un dato más simple ayuda:

def test_descuento_del_15_por_ciento():
    monto = 1000

    descuento = calcular_descuento(monto)

    assert descuento == 150

El valor 1000 facilita calcular mentalmente el resultado esperado.

18.7 Usar valores que revelen errores

Los datos simples no deben ser tan simples que oculten defectos. Por ejemplo, si probamos una multiplicación solo con 1, podríamos no detectar errores.

def test_calcular_total_debil():
    assert calcular_total(precio=100, cantidad=1) == 100

Este caso es válido, pero quizá no revela si la cantidad realmente se usa. Un caso con cantidad mayor a 1 puede ser más informativo:

def test_calcular_total_multiplica_precio_por_cantidad():
    assert calcular_total(precio=100, cantidad=3) == 300

18.8 Datos que muestran límites

Cuando la prueba cubre un límite, el dato debe mostrarlo de forma explícita.

def test_edad_18_puede_registrarse():
    edad_minima_permitida = 18

    resultado = puede_registrarse(edad_minima_permitida)

    assert resultado == True

El nombre de la variable explica por qué se eligió 18. Esto puede ser útil cuando el valor tiene significado de negocio.

18.9 Fechas como datos de prueba

Las fechas deben elegirse con cuidado. Usar la fecha actual del sistema puede volver la prueba inestable. Es mejor pasar una fecha explícita.

from datetime import date


def cupon_vigente(fecha_actual):
    return fecha_actual <= date(2026, 12, 31)


def test_cupon_vigente_en_fecha_limite():
    fecha_limite = date(2026, 12, 31)

    assert cupon_vigente(fecha_limite) == True

La prueba será repetible porque no depende del día en que se ejecuta.

18.10 Textos como datos de prueba

Al probar texto, conviene usar cadenas que hagan visible la transformación o validación.

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


def test_normalizar_nombre_elimina_espacios_y_capitaliza():
    nombre_con_espacios = "  ana perez  "

    resultado = normalizar_nombre(nombre_con_espacios)

    assert resultado == "Ana Perez"

El dato incluye espacios y minúsculas porque eso es justamente lo que la función debe corregir.

18.11 Listas como datos de prueba

Las listas deben ser lo bastante pequeñas para leerse, pero suficientes para probar la regla.

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


def test_filtrar_pares_devuelve_solo_pares():
    numeros = [1, 2, 3, 4]

    resultado = filtrar_pares(numeros)

    assert resultado == [2, 4]

La lista contiene pares e impares. Si solo tuviera pares, no verificaría bien el filtrado.

18.12 Objetos como datos de prueba

Cuando necesitamos objetos, conviene crearlos con la menor información necesaria.

class Cliente:
    def __init__(self, tipo):
        self.tipo = tipo


def test_cliente_vip_recibe_descuento():
    cliente = Cliente(tipo="vip")

    descuento = calcular_descuento(cliente, monto=1000)

    assert descuento == 150

Si la regla solo depende del tipo de cliente, no hace falta preparar nombre, email, dirección o historial de compras.

18.13 Helpers para crear datos

Cuando varios tests necesitan objetos similares, podemos crear funciones auxiliares. Pero deben mantenerse simples y explícitas.

def crear_cliente(tipo="comun"):
    return Cliente(tipo=tipo)


def test_cliente_vip_recibe_descuento():
    cliente = crear_cliente(tipo="vip")

    descuento = calcular_descuento(cliente, monto=1000)

    assert descuento == 150

El helper reduce repetición sin ocultar el dato importante: el cliente es VIP.

18.14 Helpers demasiado ocultos

Un helper puede volverse problemático si esconde demasiada información.

def test_cliente_vip_recibe_descuento():
    cliente = crear_cliente_para_prueba_completa()

    descuento = calcular_descuento(cliente, monto=1000)

    assert descuento == 150

No sabemos si el cliente es VIP, común o empleado sin ir a leer el helper. Si ese dato es central para la prueba, debe verse en el propio test o pasarse como argumento explícito.

18.15 Fixtures pequeñas

Una fixture es una preparación reutilizable. Puede ser útil cuando muchas pruebas necesitan el mismo contexto. Sin embargo, una fixture grande puede ocultar demasiados detalles.

Buena fixture:

def carrito_vacio():
    return Carrito()

Fixture riesgosa:

def contexto_completo_de_compra():
    # Crea usuario, carrito, productos, descuentos, direccion y pago
    ...

La segunda puede ser útil en pruebas de integración, pero para pruebas unitarias puede ser demasiado amplia.

18.16 Reutilizar sin perder claridad

Reutilizar preparación puede ahorrar repetición, pero no debe sacrificar legibilidad. La regla práctica es: lo importante para entender el caso debe quedar visible.

def test_cliente_empleado_recibe_descuento_20():
    cliente = crear_cliente(tipo="empleado")

    descuento = calcular_descuento(cliente, monto=1000)

    assert descuento == 200

El helper se reutiliza, pero el tipo de cliente sigue explícito.

18.17 Datos compartidos y acoplamiento

Compartir datos globales entre pruebas puede generar acoplamiento. Si una prueba modifica esos datos, otra prueba puede fallar dependiendo del orden de ejecución.

Ejemplo riesgoso:

usuarios = []


def test_agregar_usuario():
    usuarios.append("Ana")

    assert len(usuarios) == 1

Si otra prueba usa la misma lista, el resultado puede cambiar. En pruebas unitarias, conviene que cada prueba prepare sus propios datos o reciba datos nuevos desde una fixture controlada.

18.18 Tabla de buenas prácticas

Práctica Motivo
Usar datos mínimos. Reduce ruido y facilita lectura.
Nombrar datos importantes. Explica por qué fueron elegidos.
Evitar valores mágicos. Hace visible la intención.
Usar valores que revelen errores. Evita pruebas que pasan por casualidad.
Crear helpers simples. Reduce repetición sin ocultar lo importante.
Evitar datos globales modificables. Mantiene independencia entre pruebas.

18.19 Lista de comprobación

Al preparar datos de prueba, revisa:

  • ¿Cada dato preparado se usa realmente?
  • ¿Los valores elegidos revelan la regla que se prueba?
  • ¿Hay valores mágicos que deberían nombrarse?
  • ¿La preparación es más compleja que el comportamiento probado?
  • ¿Los helpers ocultan información importante?
  • ¿La prueba puede ejecutarse independientemente de otras?

18.20 Qué debes recordar de este tema

  • Los datos de prueba deben ser simples, claros y relacionados con el comportamiento probado.
  • Preparar datos innecesarios vuelve la prueba más difícil de leer.
  • Los nombres expresivos ayudan a explicar la intención del caso.
  • Conviene evitar valores mágicos.
  • Los datos deben ser simples, pero también capaces de revelar errores.
  • Los helpers y fixtures deben reducir repetición sin ocultar lo importante.
  • Las pruebas deben evitar datos compartidos modificables.

18.21 Conclusión

Los datos de prueba son parte central de la claridad de una prueba unitaria. Datos bien elegidos hacen visible la regla, reducen ruido y facilitan el diagnóstico cuando algo falla.

La meta no es crear datos realistas en exceso, sino datos precisos para el comportamiento que queremos verificar. Simples, expresivos y suficientes.

En el próximo tema veremos cómo organizar archivos y suites de pruebas para mantener una estructura clara a medida que el proyecto crece.