Parametrización en Pytest
Cuando se escriben pruebas automatizadas, la parametrización se usa con frecuencia, permitiendo que la misma prueba se ejecute con diferentes entradas. Este enfoque aborda varios desafíos en la automatización de pruebas. Esta guía explora los problemas que la parametrización puede resolver, la parametrización en Pytest y su integración con Allure Report.
1. Preparación
Prerrequisitos
Asegúrate de cumplir con los siguientes prerrequisitos:
Lista de dependencias
Esta guía utiliza los siguientes paquetes:
Ejemplo de código
El código fuente completo utilizado en esta guía está disponible en https://github.com/allure-examples/guide-pytest-parameterization.
Configuración
Para ejecutar los ejemplos en esta guía:
Instala Python
Instala Allure Report
Descarga un proyecto nuevo con Pytest desde Allure Start
En el directorio del proyecto, utiliza el script correspondiente a tu sistema operativo:
shell./run.sh
powershell.\run.bat
2. ¿Por qué usar parametrización?
Comencemos con un ejemplo sencillo: queremos probar si la suma de dos números se calcula correctamente.
def add(a, b):
return a + b
def test_sum():
s = add(1, 2)
assert s == 3
Ahora, ¿cómo podemos ejecutar esta prueba con varios conjuntos de datos?
Una opción es implementar un bucle a través de un conjunto de datos dentro de la propia prueba. Por ejemplo:
def test_sum_loop():
data = [
[1, 2, 3],
[-1, 1, 0],
[0, 0, 0]
]
for x, y, ttl in data:
s = add(x, y)
assert s == ttl
Esta opción tiene muchas desventajas:
- Si una aserción falla para una entrada específica, toda la prueba falla inmediatamente sin informar los resultados de los casos restantes.
- Perdemos visibilidad sobre qué conjunto de entradas específicas causó el fallo de la prueba, lo que dificulta la depuración y el mantenimiento.
- Un informe de prueba solo muestra el nombre de la prueba, no el ciclo particular del bucle. En esta configuración, no hay opción para hacerlo más legible.
El framework Pytest proporciona una mejor alternativa: parametrización.
Para parametrizar una prueba con Pytest, puedes usar el decorador @pytest.mark.parametrize
con nombres de parámetros y una lista de entradas de prueba. Pytest ejecuta la función de prueba con cada entrada de prueba, creando casos de prueba separados. Con esta funcionalidad, podemos reescribir la prueba anterior:
data = [
[1, 2, 3],
[-1, 1, 0],
[0, 0, 0]
]
@pytest.mark.parametrize("x,y,ttl", data)
def test_sum_parameterized(x, y, ttl):
s = add(x, y)
assert s == ttl
Con Pytest, también puedes parametrizar fixtures.
Las fixtures parametrizadas permiten ejecutar la misma prueba varias veces con diferentes valores de fixture como entradas de prueba. Por ejemplo:
@pytest.fixture(params=[0, 1, 2])
def sum_arguments(request):
yield prepare_test_data(request.param)
def test_sum_simple(sum_arguments):
x, y, ttl = sum_arguments
assert add(x, y) == ttl
def test_sum_keyword(sum_arguments):
x, y, ttl = sum_arguments
assert sum((x, y)) == ttl
En este ejemplo, definimos una fixture sum_arguments
y pasamos una lista como argumento params
de la fixture. La función prepare_test_data
representa el código que prepara los datos de prueba que se utilizarán en las funciones de prueba a continuación. En el repositorio de ejemplo, esta función lee los datos de una variable global. Pero también podría haberlos obtenido de otra fuente: un archivo, una base de datos, etc.
Este enfoque es útil si necesitas parametrizar múltiples pruebas usando el mismo conjunto de datos.
Tanto las pruebas parametrizadas como las fixtures parametrizadas ofrecen ventajas significativas:
- Cada prueba se ejecuta varias veces con diferentes entradas. Si una ejecución falla, aún recibirás retroalimentación de las demás.
- Los datos de prueba se definen en un solo lugar y se accede a ellos mediante variables (
x
,y
ysum
), lo que es mucho más conveniente. - En un informe de prueba, múltiples ejecuciones de una prueba parametrizada con diferentes entradas se muestran por separado.
- La preparación de datos está separada de las funciones de prueba, haciendo que el código sea más compacto y fácil de leer.
3. Parametrización en Allure
Allure Report se integra con Pytest y admite pruebas automatizadas parametrizadas. Un ejemplo de pruebas parametrizadas en Allure Report:
Allure Report reconoce todos los mecanismos de parametrización de Pytest, incluyendo fixtures, la anotación @pytest.mark.parametrize
y el hook pytest_generate_tests
, y muestra toda la información de las pruebas correctamente sin necesidad de agregar código adicional.
Para las pruebas con fixtures parametrizadas, el informe muestra los valores originales pasados a las fixtures.
Interpolación del nombre de la prueba
Puedes pasar parámetros de Pytest al título de la prueba que aparece en Allure Report con @allure.title
:
import pytest
import allure
@pytest.mark.parametrize("username", ["j_doe", "admin"])
@allure.title("Check {username} can log in")
def test_login(username):
# lógica de prueba
Las reglas de formato para @allure.title
son las mismas que para str.format()
.
Agregar parámetros dinámicos
Puedes agregar manualmente parámetros y valores a los casos de prueba en Allure Report con la función allure.dynamic.parameter()
:
def test_sum():
x = 1
y = 2
ttl = 3
allure.dynamic.parameter("x", x)
allure.dynamic.parameter("y", y)
allure.dynamic.parameter("sum", ttl)
assert add(x, y) == ttl
La función agrega un nuevo parámetro al informe, pero no cambia ni oculta los parámetros existentes.
allure.dynamic.parameter()
también afecta el historial y los reintentos de prueba. De forma predeterminada, Allure Report separa todos los reintentos y el historial de una prueba parametrizada según sus valores de parámetro.
A veces, es posible que desees evitar este comportamiento para un nuevo parámetro. Para hacerlo, establece excluded
en True
. Esto es útil si agregas un parámetro cuyo valor cambia en cada ejecución. El siguiente ejemplo agrega una marca de tiempo a una prueba:
from datetime import datetime
def test_sum():
x = 1
y = 2
ttl = 3
allure.dynamic.parameter("Timestamp", str(datetime.now()), excluded=True)
assert add(x, y) == ttl
Sin excluded
, cada nueva ejecución crearía una nueva entrada en el informe. Consulta más información aquí.
Otro argumento útil de allure.dynamic.parameter
es mode
. Acepta los valores allure.parameter_mode.HIDDEN
o allure.parameter_mode.MASKED
.
El modo allure.parameter_mode.HIDDEN
oculta el parámetro en Allure Report. Ten en cuenta que el parámetro aún puede extraerse del directorio allure_results
. Un caso de uso común es ocultar un parámetro cuyo único propósito es separar los resultados de pruebas obtenidos en diferentes entornos de prueba. Puedes aprender más aquí.
El modo allure.parameter_mode.MASKED
ofusca el valor del parámetro en Allure Report. Usa este modo para contraseñas, tokens y otros parámetros sensibles.
Manejo de valores grandes
Cuando parametrizas pruebas, los valores pueden volverse muy grandes. Por ejemplo, podrías tener:
@pytest.mark.parametrize(["user"], [
# Puedes tener un objeto más grande aquí
{ "name": "John", "surname": "Doe", "login": "j_doe", "role": "admin" },
])
def test_user_login(user):
login = user["login"]
# lógica de prueba
Los valores parametrizados grandes pueden causar problemas: pueden hacer que el informe de prueba ocupe mucho más espacio en disco y sea difícil de leer.
Para evitar estos problemas, recomendamos parametrizar con las claves del mapa y buscar los valores en las funciones de prueba. También recomendamos parametrizar fixtures, ya que permiten una inicialización personalizada. Por ejemplo:
USERS = {
"j_doe": { "name": "John", "surname": "Doe", "login": "j_doe", "role": "admin" },
}
@pytest.fixture(params=USERS.keys())
def user(request):
# Puedes añadir una inicialización personalizada aquí (por ejemplo, cargar datos de DB ro algo más)
yield USERS[request.param]
def test_user_login(user):
login = user["login"]
# Si queremos mostrar más datos sobre el usuario, debemos añadirlos como parámetro `excluded` de Allure
allure.dynamic.parameter("Full name", f"{user["name"]} {user["surname"]}", excluded=True)
# lógica de prueba
4. Conclusión
Allure Report admite de forma nativa la parametrización en Pytest, sin necesidad de configuración adicional ni código extra. Esta técnica de prueba permite ejecutar la misma prueba con múltiples conjuntos de datos, eliminando la redundancia y evitando la complejidad de iterar manualmente sobre los casos de prueba.
Con la parametrización, las pruebas se vuelven más estructuradas, legibles y fáciles de mantener. Además, Allure captura y muestra sin problemas las ejecuciones de pruebas parametrizadas en sus informes, lo que facilita el análisis de resultados y la identificación de fallos en diferentes entradas. Esto mejora tanto la cobertura de las pruebas como la eficiencia en la depuración.