Una suite de pruebas unitarias no se mantiene útil por accidente. Al principio puede ser pequeña, rápida y fácil de entender, pero con el tiempo aparecen más casos, más datos, más dependencias y más decisiones de diseño.
Si no cuidamos la suite, puede volverse lenta, frágil o confusa. Cuando eso ocurre, el equipo empieza a ejecutarla menos, a ignorar fallas intermitentes o a modificar pruebas sin entender qué protegían.
Este tema reúne buenas prácticas para mantener pruebas unitarias rápidas, claras y confiables durante la vida del proyecto.
Una suite rápida se ejecuta en poco tiempo y permite recibir retroalimentación frecuente. Una suite clara permite entender qué comportamiento falló y por qué importa.
La velocidad y la claridad trabajan juntas. Si las pruebas son rápidas pero incomprensibles, el diagnóstico será costoso. Si son claras pero muy lentas, se ejecutarán poco. El objetivo es sostener ambas cualidades.
Una prueba pequeña tiene una intención principal, una preparación acotada y una verificación concreta. Esto facilita leerla, entenderla y corregirla cuando falla.
def test_cliente_con_1000_puntos_es_vip():
cliente = Cliente(puntos=1000)
assert cliente.es_vip() is True
La prueba anterior es pequeña porque muestra una regla específica. No intenta crear un flujo completo de compras, registrar pagos ni consultar una base de datos.
Los nombres deben describir el comportamiento esperado. Esto ayuda a interpretar fallas sin abrir inmediatamente el cuerpo de la prueba.
def test_retirar_mas_que_el_saldo_genera_error():
cuenta = Cuenta(saldo=500)
with pytest.raises(ValueError):
cuenta.retirar(800)
El nombre comunica la regla. Si esta prueba falla, sabemos que el problema está relacionado con el rechazo de retiros superiores al saldo.
Los datos deben explicar el caso. Cuando sea posible, conviene elegir valores fáciles de verificar mentalmente.
def test_descuento_del_20_por_ciento():
assert aplicar_descuento(1000, 20) == 800
El valor 1000 hace que el resultado esperado sea evidente. Si la prueba necesita un dato complejo, debe haber una razón clara: un caso límite, una regla especial o una regresión previa.
Una suite clara se enfoca en lo que la unidad promete hacer, no en cada detalle interno de cómo lo hace. Esto permite refactorizar el código productivo sin romper pruebas innecesariamente.
def test_agregar_producto_incrementa_total():
carrito = Carrito()
carrito.agregar("mouse", 1000)
assert carrito.total() == 1000
La prueba no revisa la lista interna ni el nombre de variables privadas. Verifica el resultado que importa para quien usa la clase.
Para mantener velocidad y determinismo, las pruebas unitarias deben evitar bases de datos reales, APIs externas, red y archivos persistentes cuando no son necesarios.
Si una regla puede probarse con datos en memoria, esa suele ser la mejor opción:
def test_producto_sin_stock_no_puede_venderse():
producto = Producto(stock=0, activo=True)
assert producto.puede_venderse(1) is False
Las pruebas que verifican conexión con base de datos o servicios externos son valiosas, pero pertenecen a otro nivel de testing.
Cada prueba debe poder ejecutarse sola, en cualquier orden y con el mismo resultado. Para eso, debe preparar su propio estado o recibirlo desde una fixture limpia.
@pytest.fixture
def carrito_vacio():
return Carrito()
def test_carrito_inicia_vacio(carrito_vacio):
assert carrito_vacio.cantidad() == 0
def test_agregar_producto_incrementa_cantidad(carrito_vacio):
carrito_vacio.agregar("mouse", 1000)
assert carrito_vacio.cantidad() == 1
pytest crea una instancia nueva de la fixture por prueba, salvo que se indique otro alcance. Esto ayuda a evitar contaminación de estado.
Las pruebas deben ser determinísticas. Si una unidad depende de la fecha actual o de números aleatorios, conviene inyectar esos valores o usar dobles controlados.
def test_cupon_vencido_no_es_valido():
hoy = date(2026, 5, 10)
cupon = Cupon(fecha_vencimiento=date(2026, 5, 9))
assert cupon.es_valido_en(hoy) is False
Pasar la fecha explícitamente evita que la prueba cambie de resultado según el día de ejecución.
La organización de archivos debe ayudar a encontrar pruebas. Una convención simple es crear archivos de prueba que correspondan a módulos, clases o áreas de comportamiento.
tests/
test_carrito.py
test_cuenta.py
test_validaciones_usuario.py
test_reglas_pedido.py
No existe una única estructura correcta. Lo importante es que el equipo pueda ubicar rápidamente dónde están las pruebas relacionadas con una unidad o regla.
Una causa frecuente de suites lentas es mezclar pruebas unitarias con integración, end-to-end o pruebas que requieren infraestructura externa.
Conviene separarlas por carpeta, marca o configuración:
tests/
unit/
integration/
e2e/
Así podemos ejecutar pruebas unitarias con frecuencia y reservar las pruebas más costosas para momentos adecuados del flujo de desarrollo.
Una suite rápida vale más cuando se ejecuta seguido. Durante el desarrollo, conviene correr las pruebas relacionadas con el cambio actual y luego una suite más amplia antes de integrar.
pytest tests/unit/test_carrito.py
La ejecución frecuente reduce la distancia entre el cambio que introdujo un problema y el momento en que lo detectamos.
Las fixtures ayudan a reducir preparación repetida, pero deben mantenerse pequeñas y explícitas. Una fixture debe describir claramente el estado que entrega.
@pytest.fixture
def cuenta_con_saldo_500():
return Cuenta(saldo=500)
El nombre comunica el dato relevante. En cambio, una fixture llamada setup o contexto obliga a buscar su contenido para entender la prueba.
Las pruebas parametrizadas reducen repetición cuando varios casos tienen la misma estructura. La tabla de datos debe ser pequeña y significativa.
import pytest
@pytest.mark.parametrize("edad, esperado", [
(17, False),
(18, True),
(19, True),
])
def test_puede_registrarse_segun_edad(edad, esperado):
assert puede_registrarse(edad) is esperado
Este uso es claro porque todos los casos giran alrededor del mismo límite. Si cada fila representa una regla distinta, puede ser mejor escribir pruebas separadas.
La aserción debe mostrar qué resultado importa. Una aserción demasiado genérica puede pasar aunque el comportamiento correcto no esté garantizado.
def test_productos_disponibles():
productos = obtener_productos_disponibles()
assert "teclado" in productos
Esta aserción es más informativa que comprobar solamente que la lista no está vacía, siempre que el comportamiento esperado sea incluir el producto con stock.
Cuando cambia una funcionalidad, también debe revisarse si las pruebas siguen expresando el comportamiento correcto. Las pruebas son parte del código del proyecto.
En una revisión conviene mirar:
Una prueba que no verifica nada importante puede confundir más de lo que ayuda. Antes de eliminarla, conviene entender si protegía algún comportamiento que debería conservarse.
Algunas pruebas candidatas a revisión son:
La meta no es tener más pruebas, sino tener mejores señales.
Una falla intermitente no debe ignorarse. Si el equipo se acostumbra a repetir la suite hasta que pase, pierde confianza en todas las pruebas.
Cuando una prueba falla a veces, conviene revisar:
Una suite clara debe producir resultados confiables. Una falla intermitente es una deuda técnica real.
Cuando una suite empieza a tardar demasiado, conviene identificar las pruebas más lentas. pytest permite mostrar duraciones:
pytest --durations=10
Las pruebas más lentas suelen revelar dependencias externas, preparación costosa o casos que quizá pertenecen a otro nivel de testing.
No hace falta optimizar prematuramente cada milisegundo, pero sí conviene evitar que la suite unitaria pierda su velocidad característica.
Una suite es más fácil de leer cuando las pruebas siguen convenciones similares: nombres, estructura, ubicación de fixtures, uso de parametrización y estilo de aserciones.
Por ejemplo, el equipo puede acordar:
La consistencia reduce el esfuerzo de leer pruebas nuevas.
Esta tabla resume prácticas útiles para mantener una suite unitaria sana.
| Práctica | Beneficio | Ejemplo |
|---|---|---|
| Nombres por comportamiento. | Fallas más fáciles de interpretar. | test_edad_18_puede_registrarse |
| Datos simples. | Resultados esperados más claros. | 1000 para porcentajes. |
| Estado limpio por prueba. | Independencia y determinismo. | Fixture con alcance por función. |
| Separar niveles de prueba. | Suite unitaria más rápida. | tests/unit y tests/integration |
| Refactorizar pruebas. | Menos ruido y mejor mantenimiento. | Extraer preparación repetida. |
Una revisión periódica de la suite puede usar preguntas como estas:
Estas preguntas ayudan a detectar problemas antes de que la suite se vuelva difícil de usar.
Mantener una suite rápida y clara es una tarea continua. No alcanza con escribir pruebas al comienzo; hay que revisarlas, refactorizarlas y adaptarlas cuando el sistema cambia.
Una buena suite de pruebas unitarias funciona como una herramienta diaria de trabajo. Debe ejecutarse con frecuencia, fallar con mensajes útiles y ayudar a modificar código con mayor confianza.
En el próximo tema cerraremos el curso con un caso práctico integrador, aplicando muchas de las ideas estudiadas para probar una unidad completa.