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.
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.
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:
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:
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.
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.
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.
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.
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
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.
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.
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.
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.
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.
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:
Comentarios poco útiles:
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.
Algunos problemas comunes son:
Una preparación clara debe permitir entender rápidamente en qué condiciones se ejecuta la unidad.
En la etapa de ejecución, los errores más habituales son:
Si no podemos señalar la línea principal de ejecución, la prueba probablemente está mezclando demasiadas ideas.
En la verificación, los problemas más comunes son:
La verificación debe responder con precisión: ¿qué resultado demuestra que la unidad se comportó correctamente?
| 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. |
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.