Las funciones puras son una de las mejores puertas de entrada a las pruebas unitarias. Permiten concentrarse en entradas, salidas y resultados esperados sin depender de bases de datos, archivos, red, reloj del sistema ni estado global.
Cuando una función pura está bien diseñada, probarla suele ser directo: se le entregan datos, se ejecuta y se verifica el valor que devuelve.
Una función pura cumple dos condiciones principales:
Esto significa que no modifica variables globales, no escribe archivos, no imprime como parte de su comportamiento principal, no consulta servicios externos y no cambia objetos recibidos de forma inesperada.
La siguiente función es pura porque solo depende de sus parámetros y devuelve un resultado.
def sumar(a, b):
return a + b
def test_suma_dos_numeros():
assert sumar(2, 3) == 5
No hace falta preparar un entorno complejo. La prueba expresa con claridad qué entrada se usa y qué salida se espera.
Las funciones puras reducen la incertidumbre. Si una prueba falla, la causa está en la lógica de la función o en el resultado esperado de la prueba, no en un archivo que cambió, una conexión caída o una fecha distinta.
Esta característica hace que las pruebas sean rápidas, repetibles y fáciles de entender para quien lee el código por primera vez.
Una buena prueba para una función pura debe mostrar con precisión qué datos entran y qué resultado debe salir.
def calcular_iva(importe, porcentaje):
return importe * porcentaje / 100
def test_calcula_iva_del_veintiuno_por_ciento():
resultado = calcular_iva(1000, 21)
assert resultado == 210
El nombre de la prueba, los datos y la aserción cuentan la misma historia.
El primer caso que conviene probar suele ser el más representativo: aquel que describe el uso normal de la función.
def aplicar_descuento(precio, porcentaje):
return precio - precio * porcentaje / 100
def test_aplica_descuento_a_un_precio():
assert aplicar_descuento(2000, 10) == 1800
Este tipo de prueba confirma la intención principal de la función antes de avanzar hacia límites o situaciones menos frecuentes.
Los casos límite siguen siendo importantes aunque la función sea pura. Una comparación o una fórmula mal escrita puede fallar justo en el borde.
def clasificar_temperatura(grados):
if grados < 0:
return "bajo cero"
if grados == 0:
return "cero"
return "sobre cero"
def test_cero_se_clasifica_como_cero():
assert clasificar_temperatura(0) == "cero"
Muchas funciones reciben listas, textos o colecciones. En esos casos, una entrada vacía puede revelar errores importantes.
def promedio(numeros):
if len(numeros) == 0:
return 0
return sum(numeros) / len(numeros)
def test_promedio_de_lista_vacia_es_cero():
assert promedio([]) == 0
La prueba documenta una decisión: en este diseño, el promedio de una lista vacía se considera cero.
Si una función acepta números, no hay que asumir que todos serán positivos. Los valores negativos pueden cambiar el sentido de un cálculo.
def diferencia_absoluta(a, b):
diferencia = a - b
if diferencia < 0:
return -diferencia
return diferencia
def test_diferencia_absoluta_con_resultado_negativo_intermedio():
assert diferencia_absoluta(3, 8) == 5
Las funciones que limpian o transforman textos son buenas candidatas para pruebas unitarias, porque pequeños detalles suelen importar.
def crear_slug(titulo):
return titulo.strip().lower().replace(" ", "-")
def test_crea_slug_en_minusculas_y_con_guiones():
assert crear_slug(" Curso de Testing ") == "curso-de-testing"
La prueba verifica el comportamiento esperado sin analizar cada operación interna.
Una función pura no debería modificar una lista recibida como argumento si su contrato indica que devuelve una nueva lista.
def obtener_pares(numeros):
return [numero for numero in numeros if numero % 2 == 0]
def test_obtener_pares_no_modifica_la_lista_original():
numeros = [1, 2, 3, 4]
resultado = obtener_pares(numeros)
assert resultado == [2, 4]
assert numeros == [1, 2, 3, 4]
En una prueba de función pura, el resultado esperado debe ser explícito cuando sea posible. Si la prueba repite el mismo cálculo, deja de aportar confianza.
def total_con_envio(total, envio):
return total + envio
def test_total_con_envio():
resultado = total_con_envio(1500, 300)
assert resultado == 1800
Es mejor escribir 1800 que repetir 1500 + 300 como aserción
principal.
Muchas veces una unidad es difícil de probar porque mezcla cálculo con entrada y salida. Una estrategia útil es separar la lógica pura de la parte que interactúa con el exterior.
def calcular_total(precios):
return sum(precios)
def mostrar_total(precios):
total = calcular_total(precios)
print(f"Total: {total}")
La función calcular_total es pura y fácil de probar. La función
mostrar_total tiene un efecto secundario porque imprime en pantalla.
Cuando un flujo mezcla varias responsabilidades, conviene extraer la decisión o el cálculo a una función pura y probar esa parte por separado.
def calcular_puntaje(respuestas_correctas, total_preguntas):
if total_preguntas == 0:
return 0
return respuestas_correctas * 100 / total_preguntas
def test_calcula_puntaje_porcentual():
assert calcular_puntaje(8, 10) == 80
Así la prueba se concentra en la regla y no en cómo se cargan o muestran los datos.
Una función pura también puede rechazar argumentos inválidos. La ausencia de efectos secundarios no impide que tenga un contrato estricto.
def dividir(a, b):
if b == 0:
raise ValueError("El divisor no puede ser cero")
return a / b
def test_dividir_rechaza_divisor_cero():
try:
dividir(10, 0)
assert False
except ValueError as error:
assert str(error) == "El divisor no puede ser cero"
Como las funciones puras suelen ser pequeñas, el nombre de la prueba debe indicar el caso específico que se está verificando.
def es_par(numero):
return numero % 2 == 0
def test_cuatro_es_par():
assert es_par(4) is True
def test_cinco_no_es_par():
assert es_par(5) is False
Estos nombres son más claros que test_es_par, porque muestran dos ejemplos
concretos del comportamiento.
Una prueba de función pura suele necesitar pocas líneas. Si una prueba requiere mucha preparación, quizás la función no es tan simple como parece o está recibiendo datos demasiado complejos.
La claridad es más importante que la cantidad de verificaciones en una sola prueba. Es común escribir varias pruebas pequeñas en lugar de una prueba enorme.
Una función pura normalmente tiene estas señales:
La siguiente tabla muestra funciones candidatas a pruebas unitarias simples.
| Función | Entrada | Resultado esperado |
|---|---|---|
sumar |
2, 3 |
5 |
crear_slug |
"Curso de Testing" |
"curso-de-testing" |
promedio |
[] |
0 |
es_par |
5 |
False |
Al probar funciones puras, recuerda estas ideas:
Las funciones puras son ideales para practicar pruebas unitarias porque tienen un contrato simple: reciben datos y devuelven un resultado. Esa simplicidad permite escribir pruebas claras, rápidas y repetibles.
Cuando una parte del programa es difícil de probar, muchas veces la solución no es escribir una prueba más complicada, sino separar la lógica pura de los efectos secundarios. Este enfoque mejora tanto las pruebas como el diseño del código.