Skip to main content

Data-Driven Testing en Python con Pytest

⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.


r8vnhill/data-driven-testing-python

Data-Driven Testing (DDT) permite ejecutar un conjunto de pruebas múltiples veces, cada vez con diferentes datos de entrada. Esto es especialmente útil en el desarrollo de bibliotecas de software para validar el comportamiento de funciones en escenarios variados. Aquí, comparamos la implementación de DDT en Python con pytest con su equivalente en Kotest, destacando similitudes y diferencias, y abordando cómo simular la anidación de casos de prueba en pytest.

Ejemplo de Validación de Contraseñas

Nuestra función is_valid_password en Python verifica que una contraseña cumpla con las siguientes políticas:

  • Longitud mínima: Al menos 8 caracteres.
  • Contiene números: Al menos un dígito.
  • Contiene letras mayúsculas y minúsculas: Debe incluir ambas.
  • Contiene caracteres especiales: Debe incluir al menos un carácter especial (e.g., !@#$%^&*).

Implementación de Pruebas en pytest

Utilizamos @pytest.mark.parametrize para implementar las pruebas de DDT en pytest. Este decorador permite definir múltiples conjuntos de datos en una lista de tuplas, con cada tupla representando un caso de prueba.

src/testing/src/ddt/test/is_valid_password_test.py
import pytest
from hamcrest import assert_that, equal_to

from is_valid_password import is_valid_password


@pytest.mark.parametrize("password, expected", [
("P@ssw0rd", True),
("P@ssw0r", False),
("p@ssw0rd", False),
("P@SSW0RD", False),
("P@ssword", False),
("Password1", False),
])
def test_is_valid_password(password: str, expected: bool) -> None:
assert_that(is_valid_password(password), equal_to(expected))
¿Qué acabamos de hacer?

En este ejemplo, @pytest.mark.parametrize define múltiples casos de prueba para is_valid_password, cada uno con una contraseña y un valor esperado. pytest ejecutará la prueba con cada conjunto de datos y verificará si la función is_valid_password devuelve el resultado esperado.

Implementación de la Función is_valid_password

src/testing/src/ddt/src/is_valid_password.py
def is_valid_password(password: str) -> bool:
special_characters = "!@#$%^&*()-+"
return (
len(password) >= 8
and any(char.isdigit() for char in password)
and any(char.islower() for char in password)
and any(char.isupper() for char in password)
and any(char in special_characters for char in password)
)

Comparación con Kotest

En Kotest (Kotlin), la función withData permite un enfoque similar, parametrizando los datos de prueba en una estructura que los ejecuta sobre la misma lógica de prueba. Aunque el proceso es similar, Kotest ofrece una característica adicional para manejar casos de prueba anidados con withData anidados, una capacidad que pytest no admite de forma nativa.

Simulación de casos anidados en pytest

A diferencia de Kotest, que soporta anidación directa de withData, pytest requiere enfoques alternativos para crear combinaciones complejas de datos. Esto se puede lograr utilizando múltiples capas de @pytest.mark.parametrize y generando combinaciones de casos. A continuación, se muestra cómo podríamos manejar esta anidación simulada en pytest:

src/testing/src/ddt/test/is_valid_password_test.py
@pytest.mark.parametrize("special_char, has_special", [
("@", True), ("", False), ("!", True), ("$", True)
])
@pytest.mark.parametrize("lower_case, has_lower", [
("a", True), ("", False)
])
@pytest.mark.parametrize("upper_case, has_upper", [
("A", True), ("", False)
])
@pytest.mark.parametrize("digit, has_digit", [
("1", True), ("", False)
])
@pytest.mark.parametrize("min_length", [8, 10, 12])
def test_password_combinations(special_char: str, has_special: bool,
lower_case: str, has_lower: bool,
upper_case: str, has_upper: bool,
digit: str, has_digit: bool,
min_length: int) -> None:
password = f"{lower_case}{upper_case}{digit}{special_char}"
expected = len(password) >= min_length and has_lower and has_upper and has_digit and has_special
assert is_valid_password(password) == expected
¿Qué acabamos de hacer?

En este ejemplo, simulamos la anidación de casos de prueba en pytest utilizando múltiples capas de @pytest.mark.parametrize. Cada parámetro define un conjunto de datos para una característica específica de la contraseña, y combinamos estos conjuntos para generar casos de prueba complejos.

Resumen comparativo

CaracterísticapytestKotest
Parametrización de Pruebas@pytest.mark.parametrizewithData
Soporte de AnidaciónAnidación simulada usando múltiples @pytest.mark.parametrizeAnidación directa con withData
Simplicidad y FlexibilidadFlexible y fácil de usar, con una curva de aprendizaje bajaDSL específico de pruebas, mayor expresividad y estructura clara
Sistema de FixturesAvanzado, permite manejo eficiente de configuraciones y dependenciasMenos robusto, depende de extensiones personalizadas
Ecosistema de PluginsExtensivo, con soporte para múltiples plugins y configuraciones personalizadasMenos extenso, aunque extensible mediante plugins
Flexibilidad en Casos ComplejosUso de múltiples @pytest.mark.parametrize para generar combinaciones de casos complejoswithData anidados para combinar configuraciones complejas de manera más organizada
Informes de PruebasPersonalizables y detallados, con soporte para múltiples formatosInformes detallados con opciones de personalización según el tipo de prueba y resultado
Soporte ComunitarioMuy amplio, con una comunidad activa y numerosos recursosComunidad activa, aunque menos extensa en comparación con pytest

Beneficios y limitaciones de pytest para DDT

Beneficios

  • Parametrización Eficiente: El decorador @pytest.mark.parametrize permite definir múltiples casos de prueba de forma concisa, lo cual es útil para pruebas con datos variados.
  • Sistema de Fixtures Avanzado: El sistema de fixtures de pytest facilita la configuración y reutilización de dependencias en pruebas complejas, lo que contribuye a una estructura de pruebas bien organizada.
  • Amplio Soporte Comunitario: La comunidad activa de pytest ofrece recursos y soporte abundantes, desde documentación hasta plugins comunitarios.

Limitaciones

  • Anidación Compleja de Pruebas: Pytest no soporta la anidación de pruebas de manera nativa, lo que obliga a utilizar múltiples capas de @pytest.mark.parametrize para generar combinaciones complejas de datos, lo cual puede reducir la claridad del código.
  • Posible Complejidad con Múltiples parametrize: En casos de pruebas con múltiples parámetros y combinaciones, el uso de muchos @pytest.mark.parametrize puede complicar la lectura y el mantenimiento de las pruebas.
  • Informes Limitados sin Plugins: Aunque pytest ofrece informes básicos, para obtener un reporte altamente detallado y personalizable es necesario instalar plugins adicionales.

¿Qué aprendimos?

En esta lección, exploramos las ventajas de Data-Driven Testing (DDT) y cómo se implementa en pytest comparado con Kotest. Entendimos cómo DDT nos permite probar una función o método con una variedad de datos de entrada, asegurando que los casos de prueba sean completos y diversos sin duplicación de código. Vimos cómo pytest permite parametrizar pruebas con el decorador @pytest.mark.parametrize, y aunque no soporta anidación nativa como Kotest, es posible simularla usando múltiples capas de parametrize para casos más complejos.

Puntos clave

  1. Data-Driven Testing: Es un enfoque de prueba que permite ejecutar la misma prueba varias veces con diferentes datos de entrada, ideal para verificar funciones en escenarios variados sin duplicación de lógica de prueba.
  2. Uso de @pytest.mark.parametrize en pytest: Permite definir múltiples casos de prueba de forma concisa, facilitando la configuración de DDT.
  3. Simulación de Anidación en pytest: Aunque no soporta la anidación directa como Kotest, es posible simularla con múltiples decoradores @pytest.mark.parametrize para generar combinaciones de casos complejos.
  4. Comparativa con Kotest: Kotest ofrece una anidación de withData más estructurada y directa, mientras que pytest destaca por su flexibilidad, simplicidad y su amplio ecosistema de plugins y fixtures avanzados.
  5. Beneficios y Limitaciones de pytest: Pytest es altamente flexible y fácil de usar, con un soporte comunitario amplio y un sistema de fixtures avanzado. Sin embargo, su implementación de anidación puede volverse compleja y difícil de mantener en casos de prueba muy grandes.

Conclusión

Data-Driven Testing es esencial en la validación de bibliotecas de software, permitiendo probar funciones con múltiples conjuntos de datos sin replicar lógica de prueba. Tanto pytest como Kotest proporcionan mecanismos efectivos para implementar DDT. Pytest destaca por su flexibilidad y soporte comunitario, siendo una excelente opción para proyectos en Python, mientras que Kotest, con su estructura declarativa y anidación de datos integrada, es ideal para proyectos en Kotlin. La elección entre ambos dependerá del lenguaje de programación y de las necesidades específicas del proyecto.

Bibliografías Recomendadas