¿Qué son las pruebas parametrizadas y por que debería importarme?
Published: 2024-02-10 07:01:42

---
title: ¿Qué son las pruebas parametrizadas y por que debería importarme? 
published: true
description: 
tags: 
# cover_image: ![A lot of bunnies in the field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7a7en1e3ygwmlom1pfy.png)
# Use a ratio of 100:42 for best results.
# published_at: 2024-02-10 04:46 +0000
---

Imagina que estas absorto en la creación de un increíble proyecto, tu código es elegante, tus funciones eficientes ... pero ¡oh sorpresa!, tratando de cubrir todos los posibles escenarios desde tus pruebas ... estas empiezan a multiplicarse como conejos en primavera ...

![A lot of bunnies in the field](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j7a7en1e3ygwmlom1pfy.png)

Pero, no te animas a borrar ninguna! por que al parecer todas son igual de importantes, aunque prueban la misma función una y otra vez con diferentes párametros. 

```python
def test_login
    pass

def test_successful_login():
    pass

def test_login_without_data():
    pass

def test_login_with_incorrect_password():
    pass

def test_login_with_nonexistent_user():
    pass

def test_login_with_banned_user():
    pass
```

Entonces vuelves a ver los tests que acabas de escribir y te dices a ti mismo/a ... no puedo dejar mi código sin que las pruebas cubran tanto los escenarios positivos, como los de error y las diferentes validaciones de los mismos ... y la pregunta nace por si misma *¿Habrá una mejor forma de hacer esto?* ...

![Confused developer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/duaba7cnoei1jxsrzxgk.png)

## El Mundo de las Pruebas Parametrizadas

A principios del nuevo milenio los programadores se hicieron la misma pregunta  y nació una interesante tecnica llamada *Pruebas Parametrizadas*, que se resume basicamente en ejecutar pruebas utilizando conjuntos de datos variables.

### Generación Automatica de parámetros

Uno de las primeras implementaciones de esta tecnica fue en una biblioteca de Haskell llamada QuickCheck ... esta introdujo la idea de propiedades generales basadas en lógica que deberían ser verdad para conjuntos de datos.

![Elder Developer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l4r3wljxxzidylxgmnew.png)


Pero antes de ponerme muy teórico y aburrido, veamos un ejemplo:

```haskell
import Test.QuickCheck

-- Propiedad: La suma de una lista es mayor o igual a cada elemento de la lista
prop_suma_mayor_igual :: [Int] -> Bool
prop_suma_mayor_igual xs = sum xs >= minimum xs
```

Aquí `prop_suma_mayor_igual` es una función valida que dada una lista de números la suma siempre sera mayor o igual al minimo de la lista.

Y ahora preparate para oir sobre la magia de QuickCheck ... 

Al ejecutar la prueba esta biblioteca generara automaticamente una serie de diversos conjuntos de datos de prueba (_listas de numeros en este caso_) y verificara si `prop_suma_mayor_igual` retorna el valor esperado en cada para cada uno de ellos, si ... todo de forma autonoma, repetitiva y aleatoria.

![Computer with random numbers](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zyy27i9v1dvya5jcjbmi.png)

De esta forma no era necesario que cubras manualmente cada conjunto de parametros en un test independiente. 

Claro te preguntaras ... pero yo no uso Haskell, ¿Puedes volver a explicármelo usando algún lenguaje mas amigable? 

### Hypothesis: Magia de la Generación Automática en Python

Para llevar la magia de `QuickCheck` a Python contamos con la biblioteca `Hypothesis` la cual nos permitira generar automaticamente los conjuntos de datos que se usaran para nuestra prueba.

Digamos que quiero asegurar que aunque haga cambios, mi nuevo algoritmo mágico de optimización de rutas siempre funcione igual o mejor que el de google maps y que esta condición siempre sea valida para desde rutas simples con puntos A -> B a rutas complejas con hasta 1000 puntos

Entonces empiezo importando las funciones relevantes para mi test:

```python
from hypothesis import given, settings
from hypothesis.strategies import lists, floats, tuples
```

Luego creo la estrategia que definira con que reglas se van a generar las coordenadas:

```python
coords = tuples(floats(min_value=-1000, max_value=1000), floats(min_value=-1000, max_value=1000))
```

Y una segunda estrategia que definirá cuan amplia será esta ruta

```python
listas_coordenadas = lists(coords, min_size=1, max_size=1000)
```

Y listo, ahora escribiremos nuestro test

```python
@given(listas_coordenadas)
@settings(max_examples=10)
def test_optimizacion_ruta(coords):
    tiempo_algoritmo = optimizar_ruta(coords).tiempo_estimado 
    # gmaps.estimate es una funcion ficticia 
    # inventada con fines ilustrativos
    # y de optimizacion de codigo
    tiempo_google = gmaps.estimate(coords).time
    assert tiempo_algoritmo <= tiempo_google
```

Y wuala! ... ahora automaticamente se generaran una serie de coordenadas por los que nuestra ruta debe de pasar una diferente cada vez.

El test ejecutara 10 combinaciones distintas aleatorias lo que hace de nuestro test menos estático, y nos da la confianza de que no estemos probando solo un conjunto controlado que hayamos medido antes y pueda estar sesgado.

Y lo mejor ... todo con un solo test, claro te preguntaras y que pasa si el test falla? pues `Hypothesis` te dará un bonito mensaje que te dirá en que conjunto de datos especifico es que fallo el test ademas del feedback del assert.

```
Falsifying example: test_optimizacion_ruta(coords=[(0.0, 0.0), (0.0, 0.0)])
```

Pero ... claro asi como hay ventajas hay desventajas y si eres un prorgamador perspicaz te habras dado cuenta que la generacion aleatoria de parametros no siempre sera la mejor forma de que tus pruebas cubran los escenarios especificos que te gustaria.

### Data Driven Testing: Cuando los Datos Mandan

![Computer over a lot of books](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5aznovt6ozp1uxfxeuko.png)

Ese mismo problema tambien se plantearon los programadores en el pasado y nos brindaron otra forma muy interesante de hacer pruebas parametrizadas, inspirados por el `Data Driven Testing`, una técnica que propone la ejecución de pruebas usando conjuntos de datos acompañados de los resultados esperados, los cuales incluso pueden estar fuera de los mismos tests y ser almacenados en archivos externos o bases de datos.

Abordar el problema con esta perspectiva nos permite separar la lógica de la prueba de los conjuntos de datos, lo que significa que si mañana hay un nuevo caso que probar, solo habría que añadirlo al final del archivo o crear un registro nuevo sin tener que tocar el código del test.

### Tests Parametrizados con PyTest

Python nos provee una herramienta para testing llamada pytest la cual ya viene con funciones especificas para implementar pruebas parametrizadas con conjuntos de datos para ello usaremos `pytest.mark.parametrize`

Veamos un ejemplo, Imagina que estas escribiendo el test para una función de registro de usuario, por lo que te gustaría probar con un conjunto de datos muy especifico, que cubra los diferentes escenarios que se pudiesen presentar durante el registro.

Pero claro ... no quieres escribir un test individual para cada caso.

Entonces primero definiremos, una lista con los argumentos y el resultado esperado de la siguiente manera:

```python
datos_registro = [
    # Escenario 1: Todos los campos son válidos
    ("Usuario1", "Apellido1", "12345678", "123456789", "usuario1@example.com", True, "Registro exitoso"),
    # Escenario 2: Nombre vacío
    ("", "Apellido2", "87654321", "987654321", "usuario2@example.com", False, "Nombre no puede estar vacío"),
    # Escenario 3: Correo con formato incorrecto
    ("Usuario3", "Apellido3", "56789012", "111111111", "correo_invalido", False, "Formato de correo incorrecto"),
    # Escenario 4: Teléfono con menos de 9 dígitos
    ("Usuario4", "Apellido4", "90123456", "12345678", "usuario4@example.com", False, "Número de teléfono inválido"),
    # Escenario 5: Número de documento vacío
    ("Usuario5", "Apellido5", "", "123456789", "usuario5@example.com", False, "Número de documento no puede estar vacío"),
    # Escenario 6: Correo vacío
    ("Usuario6", "Apellido6", "34567890", "987654321", "", False, "Correo no puede estar vacío"),
    # Muchos otros escenarios
]

```
Luego escribimos nuestro test 

```python
@pytest.mark.parametrize("nombre, apellido, numero_documento, telefono, correo, resultado_esperado, mensaje_esperado", datos_registro)
def test_registro_usuario(
    nombre, apellido, numero_documento, telefono, correo, 
    resultado_esperado, mensaje_esperado
):

    # Ejecutamos la función de registro
    resultado = registrar_usuario(
        nombre, apellido, numero_documento, telefono, correo
    )

    # Validamos el resultado y el mensaje esperado
    assert resultado["resultado"] == resultado_esperado
    assert resultado["mensaje"] == mensaje_esperado
```

De esta manera mantenemos la simplicidad de un solo test, cubriendo el total de nuestros escenarios.

Y claro! este conjunto de datos puede venir de cualquier lugar, un archivo externo, una carpeta con archivos json uno por cada suite de tests, uno por test, una base de datos de prueba, etc ...

## ¿Qué hay de otros lenguajes?

Claramente imaginaras que esto no es propio de Haskell o Python sino que la comunidad de desarrolladores se ha encargado de brindarnos herramientas suficientes para que podamos crear pruebas parametrizadas en casi cualquiera de nuestros lenguajes favoritos.


## Reflexión Final:

![Zen Developer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7notzu46q8cbty680lyj.png)

Ahora, pequeño saltamontes, has explorado dos técnicas poderosas para enfrentar el desafío de las pruebas repetitivas. Este viaje te ha mostrado cómo liberarte del peso visual de pruebas que se multiplican como la gripe. Al adoptar pruebas parametrizadas con elegancia, has limpiado tus suites de pruebas, haciendo que sean más mantenibles, confiables y legibles.

Tu equipo te lo agradecerá, y tus proyectos brillarán con una nueva luz de claridad ¡Optimiza tus pruebas, mantén tu código legible y sigue creando con confianza de que la confiabilidad de tu aplicación estará muy bien resguardada!