En el tema anterior usamos datos desde listas, diccionarios, CSV y JSON. Ahora veremos cómo generar datos de prueba con Python de forma controlada.
Generar datos es útil cuando necesitamos muchos casos parecidos, objetos con valores por defecto o variaciones específicas sin escribir archivos grandes.
Generar datos no significa crear valores al azar sin control. En automatización, los datos deben ser repetibles. Si una prueba falla, debemos poder reproducir la misma situación.
Una buena generación de datos debe ser:
Una factory es una función que crea datos de prueba con valores por defecto. Crea tests/helpers/usuarios_factory.py:
def crear_usuario(nombre="Ana", email="ana@example.com", activo=True):
return {
"nombre": nombre,
"email": email,
"activo": activo,
}
Esta función permite crear usuarios válidos sin repetir el diccionario en cada prueba.
Crea tests/test_usuarios_factory.py:
from app.usuarios import obtener_email, usuario_esta_activo
from tests.helpers.usuarios_factory import crear_usuario
def test_usuario_esta_activo_con_usuario_generado_devuelve_true():
usuario = crear_usuario()
assert usuario_esta_activo(usuario) is True
def test_obtener_email_con_usuario_generado_devuelve_email_normalizado():
usuario = crear_usuario(email=" ANA@EXAMPLE.COM ")
assert obtener_email(usuario) == "ana@example.com"
La prueba pide solo el cambio que necesita. El resto de los valores queda con defaults.
Ejecuta:
python -m pytest tests/test_usuarios_factory.py
La factory no se ejecuta sola. Se usa desde las pruebas o desde fixtures.
Otra forma flexible es aceptar campos variables con **overrides:
def crear_usuario(**overrides):
usuario = {
"nombre": "Ana",
"email": "ana@example.com",
"activo": True,
}
usuario.update(overrides)
return usuario
Ahora podemos escribir:
usuario = crear_usuario(activo=False)
Esto crea un usuario igual al default, pero con activo en False.
Crea tests/helpers/productos_factory.py:
def crear_producto(**overrides):
producto = {
"nombre": "Producto de prueba",
"precio": 100,
"cantidad": 1,
"descuento": 0,
}
producto.update(overrides)
return producto
Esta factory sirve para pruebas de carrito, descuentos o precios.
Podemos crear una función para generar varios productos:
def crear_productos(cantidad):
return [
crear_producto(nombre=f"Producto {indice}", precio=100 + indice)
for indice in range(1, cantidad + 1)
]
Uso en una prueba:
from tests.helpers.productos_factory import crear_productos
def test_crear_productos_devuelve_la_cantidad_solicitada():
productos = crear_productos(3)
assert len(productos) == 3
Podemos usar la factory para probar el total del carrito:
from app.carrito import calcular_total
from tests.helpers.productos_factory import crear_producto
def test_calcular_total_con_productos_generados_devuelve_suma_total():
productos = [
crear_producto(precio=100, cantidad=2),
crear_producto(precio=50, cantidad=3),
]
assert calcular_total(productos) == 350
Los datos siguen siendo claros, pero evitamos repetir todos los campos del producto.
Una fixture puede usar una factory:
import pytest
from tests.helpers.productos_factory import crear_producto
@pytest.fixture
def productos_carrito():
return [
crear_producto(precio=100, cantidad=2),
crear_producto(precio=50, cantidad=3),
]
La fixture define un estado común y la factory simplifica la construcción de cada dato.
Algunas veces queremos variar datos, pero sin perder repetibilidad. Para eso podemos usar una semilla.
import random
def crear_codigo_numerico(seed=1234):
generador = random.Random(seed)
return generador.randint(1000, 9999)
Al usar la misma semilla, obtenemos el mismo resultado cada vez.
Crea tests/test_datos_aleatorios.py:
from tests.helpers.usuarios_factory import crear_codigo_numerico
def test_crear_codigo_numerico_con_misma_semilla_devuelve_mismo_codigo():
codigo_1 = crear_codigo_numerico(seed=1234)
codigo_2 = crear_codigo_numerico(seed=1234)
assert codigo_1 == codigo_2
La prueba no depende de azar real. El dato parece variable, pero es determinístico.
No conviene escribir pruebas que dependan de valores aleatorios sin semilla:
import random
def test_ejemplo_fragil():
valor = random.randint(1, 10)
assert valor > 5
Esta prueba puede pasar o fallar sin que el código haya cambiado. Eso la convierte en una prueba poco confiable.
Podemos generar correos a partir de un identificador:
def crear_email(indice=1):
return f"usuario{indice}@example.com"
Uso:
def test_crear_email_con_indice_devuelve_email_predecible():
assert crear_email(3) == "usuario3@example.com"
Esto es mejor que usar correos completamente aleatorios cuando solo necesitamos valores distintos.
Una función puede generar casos para pytest.mark.parametrize:
def crear_casos_descuentos():
return [
(crear_producto(precio=100, descuento=0), 100),
(crear_producto(precio=100, descuento=10), 90),
(crear_producto(precio=100, descuento=100), 0),
]
Uso:
@pytest.mark.parametrize("producto, esperado", crear_casos_descuentos())
def test_aplicar_descuento_con_productos_generados(producto, esperado):
assert aplicar_descuento(producto) == esperado
Una factory de prueba debe ayudar, no ocultar demasiado. Si una factory tiene mucha lógica, la prueba puede volverse difícil de entender.
Buenas prácticas:
Las factories son herramientas de prueba. Por eso conviene ubicarlas en tests/helpers:
tests/
|-- helpers/
| |-- usuarios_factory.py
| `-- productos_factory.py
|-- test_usuarios_factory.py
`-- test_carrito.py
No deberían mezclarse con el código de aplicación dentro de app.
Si una factory se usa mucho, conviene documentar brevemente qué valores por defecto produce.
def crear_producto(**overrides):
"""Crea un producto válido para pruebas con precio 100 y cantidad 1."""
producto = {
"nombre": "Producto de prueba",
"precio": 100,
"cantidad": 1,
"descuento": 0,
}
producto.update(overrides)
return producto
Usa comentarios o docstrings solo cuando aporten claridad real.
Crea tests/helpers/cupones_factory.py con funciones para generar cupones:
crear_cupon_validocrear_cupon_invalidocrear_cupon_con_espaciosLuego crea pruebas para validar_cupon usando esas funciones.
Archivo tests/helpers/cupones_factory.py:
def crear_cupon_valido():
return "DESC10"
def crear_cupon_invalido():
return "DESC20"
def crear_cupon_con_espacios():
return " DESC10 "
Archivo tests/test_cupones_factory.py:
from app.cupones import validar_cupon
from tests.helpers.cupones_factory import (
crear_cupon_con_espacios,
crear_cupon_invalido,
crear_cupon_valido,
)
def test_validar_cupon_con_cupon_valido_devuelve_true():
assert validar_cupon(crear_cupon_valido()) is True
def test_validar_cupon_con_cupon_invalido_devuelve_false():
assert validar_cupon(crear_cupon_invalido()) is False
def test_validar_cupon_con_espacios_devuelve_true():
assert validar_cupon(crear_cupon_con_espacios()) is True
Ejecuta:
python -m pytest tests/test_cupones_factory.py
Antes de continuar con el próximo tema, verifica lo siguiente:
tests/helpers.python -m pytest.En este tema generamos datos de prueba con Python usando factories, helpers, overrides y aleatoriedad controlada. Esta técnica permite crear datos flexibles sin depender siempre de archivos externos.
En el próximo tema automatizaremos pruebas con archivos temporales y directorios de trabajo, una habilidad útil para validar código que lee o escribe archivos.