Una de las formas más simples de comenzar con pruebas unitarias es probar unidades que devuelven un valor. La unidad recibe ciertos datos, ejecuta una regla y devuelve un resultado que podemos comparar con lo esperado.
Este tipo de prueba es común en funciones de cálculo, validación, transformación de texto, filtrado de colecciones y reglas de negocio. Suele ser fácil de entender porque la verificación se concentra en una salida concreta.
En este tema veremos cómo probar valores de retorno de manera clara, qué casos conviene elegir y qué errores evitar.
Un valor de retorno es el resultado que una función o método entrega a quien lo llama. En muchos lenguajes aparece mediante la palabra return.
def sumar(a, b):
return a + b
Si llamamos a sumar(2, 3), el valor de retorno es 5. Una prueba unitaria puede verificar exactamente eso.
def test_sumar_dos_numeros():
assert sumar(2, 3) == 5
Las pruebas sobre valores de retorno son buenas para comenzar porque suelen tener una estructura directa:
No necesitamos observar pantallas, revisar bases de datos ni controlar efectos externos. Esto reduce el ruido y permite concentrarse en la lógica.
Una función pura devuelve siempre el mismo resultado para las mismas entradas y no modifica estado externo. Este tipo de función es ideal para pruebas unitarias.
def calcular_precio_final(precio, descuento):
return precio - (precio * descuento / 100)
def test_calcular_precio_final_con_descuento_del_10_por_ciento():
resultado = calcular_precio_final(1000, 10)
assert resultado == 900
Si ejecutamos la prueba muchas veces, el resultado será el mismo mientras la función no cambie. Esa repetibilidad es una característica muy valiosa.
Los cálculos numéricos son candidatos habituales para pruebas sobre valores de retorno.
def calcular_area_rectangulo(base, altura):
return base * altura
def test_calcular_area_rectangulo():
assert calcular_area_rectangulo(5, 4) == 20
La prueba verifica una entrada concreta y un resultado esperado concreto. Si la fórmula cambia accidentalmente, la prueba puede detectarlo.
Muchas funciones devuelven textos normalizados, mensajes o formatos. También pueden probarse con aserciones directas.
def normalizar_nombre(nombre):
return nombre.strip().title()
def test_normalizar_nombre_elimina_espacios_y_capitaliza():
resultado = normalizar_nombre(" ana perez ")
assert resultado == "Ana Perez"
En este caso, el valor de retorno esperado es un texto exacto. La prueba documenta cómo debe normalizarse el nombre.
Las funciones que responden preguntas suelen devolver booleanos. Por ejemplo: si una edad es válida, si un usuario tiene permisos o si un monto supera un límite.
def es_mayor_de_edad(edad):
return edad >= 18
def test_edad_17_no_es_mayor_de_edad():
assert es_mayor_de_edad(17) == False
def test_edad_18_es_mayor_de_edad():
assert es_mayor_de_edad(18) == True
Es importante probar tanto casos verdaderos como falsos. Si solo probamos el caso exitoso, podemos dejar sin revisar una parte importante de la regla.
Una función puede devolver una lista, conjunto o diccionario. En esos casos, la prueba debe decidir si importa el contenido, el orden, la cantidad o una combinación de esos aspectos.
def filtrar_pares(numeros):
return [numero for numero in numeros if numero % 2 == 0]
def test_filtrar_pares_devuelve_solo_numeros_pares():
resultado = filtrar_pares([1, 2, 3, 4, 5, 6])
assert resultado == [2, 4, 6]
Esta aserción verifica contenido y orden. Si el orden no fuera relevante, convendría usar otra forma de comparación.
Algunas funciones transforman datos y devuelven estructuras simples.
def crear_resumen_usuario(nombre, edad):
return {
"nombre": nombre,
"mayor_de_edad": edad >= 18
}
def test_crear_resumen_usuario_mayor_de_edad():
resumen = crear_resumen_usuario("Ana", 20)
assert resumen == {
"nombre": "Ana",
"mayor_de_edad": True
}
La prueba verifica la estructura completa del valor retornado. Esto es útil cuando la forma de salida es parte del contrato de la función.
Un caso normal representa una entrada habitual que esperamos que funcione. Son útiles para confirmar el comportamiento principal.
def calcular_total(precio, cantidad):
return precio * cantidad
def test_calcular_total_con_precio_y_cantidad_validos():
assert calcular_total(100, 3) == 300
Los casos normales son necesarios, pero no suficientes. Una buena suite también incluye límites y situaciones especiales.
Los casos límite se encuentran justo en el borde de una regla. Son especialmente importantes porque muchos errores aparecen en comparaciones mal escritas.
def tiene_descuento(monto):
return monto >= 10000
def test_monto_9999_no_tiene_descuento():
assert tiene_descuento(9999) == False
def test_monto_10000_tiene_descuento():
assert tiene_descuento(10000) == True
Estas pruebas protegen el límite exacto de la regla. Si alguien cambia >= por >, una de las pruebas fallará.
Algunos valores merecen atención porque suelen revelar errores:
Ejemplo con lista vacía:
def cantidad_de_items(items):
return len(items)
def test_cantidad_de_items_en_lista_vacia_es_cero():
assert cantidad_de_items([]) == 0
Algunas funciones devuelven un valor especial cuando reciben datos inválidos. Otras lanzan errores. En este tema nos concentramos en las que devuelven un valor.
def obtener_descuento(porcentaje):
if porcentaje < 0:
return 0
return porcentaje
def test_porcentaje_negativo_devuelve_descuento_cero():
assert obtener_descuento(-10) == 0
La prueba documenta cómo se maneja una entrada inválida. Es importante que esta decisión sea intencional y no accidental.
Cuando trabajamos con números decimales, puede convenir usar comparación aproximada para evitar problemas de precisión.
def calcular_promedio(numeros):
return sum(numeros) / len(numeros)
def test_calcular_promedio_decimal():
resultado = calcular_promedio([0.1, 0.2])
assert abs(resultado - 0.15) < 0.0001
La prueba no exige igualdad exacta, sino cercanía suficiente al valor esperado. Esto es habitual en cálculos con punto flotante.
Al probar valores de retorno, conviene evitar que la aserción repita la misma lógica que la función.
def test_calcular_precio_final_poco_util():
precio = 1000
descuento = 10
resultado = calcular_precio_final(precio, descuento)
assert resultado == precio - (precio * descuento / 100)
La expectativa repite la fórmula. Una versión más clara usa un valor esperado concreto:
def test_calcular_precio_final_con_descuento_del_10_por_ciento():
resultado = calcular_precio_final(1000, 10)
assert resultado == 900
Una aserción débil puede pasar aunque el resultado no sea correcto.
def test_calcular_total_debil():
total = calcular_total(100, 3)
assert total > 0
Si el total correcto es 300, pero la función devuelve 1, la prueba pasa. Una aserción más útil es:
def test_calcular_total_con_precio_y_cantidad_validos():
total = calcular_total(100, 3)
assert total == 300
| Tipo de retorno | Ejemplo de unidad | Aserción posible |
|---|---|---|
| Número | Calcular total. | assert total == 300 |
| Texto | Normalizar nombre. | assert nombre == "Ana" |
| Booleano | Validar edad. | assert es_valido == True |
| Lista | Filtrar elementos. | assert resultado == [2, 4] |
| Diccionario | Crear resumen. | assert resumen["nombre"] == "Ana" |
| Valor especial | Manejar lista vacía. | assert cantidad == 0 |
No todas las unidades se verifican solo con valores de retorno. Algunas modifican estado, escriben datos, emiten eventos o interactúan con dependencias.
Si una función devuelve True, pero además debería guardar información, quizá no alcance con comprobar el retorno. Necesitaremos observar el estado final o la interacción correspondiente.
En el próximo tema veremos justamente las pruebas sobre cambios de estado.
Al probar valores de retorno, revisa:
Las pruebas sobre valores de retorno son una base excelente para aprender pruebas unitarias. Son directas, rápidas y permiten comprobar reglas importantes con pocos elementos.
La clave está en elegir buenos casos y escribir expectativas claras. Una prueba útil no solo llama a la función: comprueba que la salida sea exactamente la esperada para una situación concreta.
En el próximo tema estudiaremos pruebas sobre cambios de estado, necesarias cuando una unidad modifica objetos en lugar de devolver toda la información como resultado.