SBN

Creando un framework para pruebas en Python

Pytest
es el rey de las herramientas de prueba de Python.
Con más de 12.000 estrellas en GitHub, una comunidad muy activa,
mejoras continuas con nuevas versiones,
y un montón de forks y plugins para ampliar su funcionalidad,
pytest es la referencia más importante
cuando necesitamos probar código en Python
.

Si buscas herramientas o frameworks de pruebas de Python en Internet,
encontrarás artículos como “Python Testing Frameworks
de E. Sales,
10 Best Python Testing Frameworks
de GeeksForGeeks,
Top 9 Python Testing Frameworks
de M. Echout,
y muchos otros posts con rankings similares
donde nunca falta pytest.

La comparación con otras herramientas parece injusta,
ya que pytest es una herramienta para pruebas unitarias y de integración,
mientras que otros frameworks compiten en un dominio muy específico.
Lettuce
y Behave,
por ejemplo,
introducen el desarrollo orientado al comportamiento (BDD, en inglés),
pero no son para todos los equipos de desarrollo;
Robot funciona para pruebas
de extremo a extremo y RPA,
pero no es para pruebas unitarias o de integración simple;
Testify,
TestProject,
y otros son proyectos muertos…
El módulo unittest
(un módulo incorporado en Python)
y nose2
podrían ser los mayores competidores de pytest,
pero carecen del soporte, la comunidad y los plugins que tiene pytest.

En Fluid Attacks,
usamos y recomendamos pytest.
Sin embargo,
decidimos encapsularlo antes de usarlo en nuestra base de código.
En este post,
quiero compartir por qué deberías probarlo
y cómo logramos un nuevo framework de pruebas
para pruebas altamente sostenibles y legibles.

Pruebas con pytest

Un blog post anterior,
De frágil a blindada
de D. Salazar,
guía nuestra intención de mejorar las pruebas unitarias y de integración.
Allí,
recomendamos el uso de un módulo de pruebas (un pytest wrapper)
debido a un asunto importante:
la estandarización.

Cuando tu equipo de desarrollo tiene muchos integrantes,
necesitas definir algunas reglas para hablar el mismo idioma.
Puedes añadir linters y formateadores
para estandarizar la sintaxis del código,
pero la forma en que los programadores escriben el código
es más difícil de estandarizar.

Algunas librerías se consideran “de opinión fuerte” (opinionated)
porque imponen un uso estandarizado
con estructuras de archivos específicas y sus propios métodos y clases.
De hecho,
pytest tiene sus propios métodos y archivos,
pero es tan flexible que parece “de opinión débil” (unopinionated).

Estas son algunas de las características más indeseadas de pytest
cuando se trata de su flexibilidad:

  • Pytest tiene una forma oficial para falsificar métodos
    y clases para pruebas,
    pero puedes hacer lo mismo utilizando otras librerías
    (p. ej., unittest) sin tener conflictos.

  • Los fixtures de Pytest realmente hacen magia.
    Puedes modificar todo el comportamiento de la prueba con fixtures
    sin hacer referencia directa a esas funciones
    y estropear los flujos de prueba esperados.

  • Puedes poner las pruebas donde quieras,
    incluso al lado del código funcional.

  • Puedes utilizar servicios reales o simulados
    porque la ejecución de pytest no está aislada.

Si reúnes a un equipo en crecimiento
en el que todos tienen experiencias diferentes trabajando con pytest,
puedes acumular toneladas de deuda técnica, código ilegible
y WTFs/minuto de incremento exponencial.

Calidad del código

De OSNews.

Por eso,
decidimos implementar nuestro pytest wrapper,
el cual incluye otras librerías de pruebas como moto,
freezegun, o coverage-py
para escribir código de prueba de una sola manera.

Hablemos de nuestras nuevas directrices y sus ventajas:

Prohibición de pytest

Prohibición de pytest

Gracias a la librería importlinter,
prohibimos el uso de pytest y unittest en cualquier lugar
que no sea nuestro framework de pruebas.
Nos protegemos contra el mal uso mediante la eliminación de pytest fixtures,
unittest mocks, y cualquier característica innecesaria
que pueda ser introducida luego.
De este modo,
el módulo de pruebas puede utilizar pytest
y exportar las herramientas más importantes para su uso.

Utilidades encapsuladas

pytest.raises (para capturar excepciones),
pytest.mark.parametrize (para manejar múltiples casos por prueba)
y freezegun.freeze_time
(para utilizar un tiempo falso para ejecutar la prueba)
son las funciones más comunes que utilizamos.
Nosotros encontramos una manera de encapsularlas
en funciones que pueden ser importadas desde nuestro módulo de pruebas,
haciéndolas fáciles de usar
y permitiéndonos documentar ejemplos de cómo implementarlas.

Utilidades encapsuladas

Servicios simulados

La plataforma de Fluid Attacks utiliza servicios de AWS.
Nosotros decidimos simularlos con moto
para lograr pruebas muy sencillas y rápidas
gracias a la simulación en memoria de servicios como DynamoDB y S3.
Sin embargo,
moto requiere algo de código repetitivo para ejecutarse
y garantizar el aislamiento entre las pruebas.
Los desarrolladores podrían añadir lógica compleja dentro de las pruebas
para simular servicios
u olvidar la forma correcta de limpiar estas simulaciones
porque algunos de los pasos de copiar y pegar se pasan por alto.

Por tal motivo,
incluimos un decorador en nuestro módulo de pruebas
para iniciar el entorno falso de AWS.
También encapsulamos todo el inicio y la limpieza
para garantizar el aislamiento de las pruebas y simplificarlas.
Los desarrolladores solo necesitan la documentación
para saber cómo precargar datos o archivos para las pruebas
mediante un enfoque declarativo estándar.

Modos procedimental y declarativo

Fakers para objetos

Dejando atrás la discusión sobre las diferencias entre fakers, mocks,
stubs y spies,
necesitábamos crear datos falsos.
Para ello implementamos Fakers,
una colección de funciones diseñadas para devolver objetos falsos específicos
basados en los tipos de datos.

Los fakers son fáciles de implementar
y pueden llamar a otros fakers para rellenar campos anidados.
Los desarrolladores pueden modificar cualquier campo
al llamarlos para mayor flexibilidad.
Este enfoque reduce significativamente el código repetitivo,
permitiendo la creación de objetos de prueba bien estructurados
sin necesidad de definir manualmente cada propiedad.

Cobertura por módulo

Nos sumergimos a fondo en la librería coverage-py
para conseguir una solución modular.
Esta potente herramienta genera informes detallados
sobre la cobertura de las pruebas para el código ejecutado,
sirviendo como un recurso crítico para identificar lagunas
en nuestro conjunto de pruebas.
Analizando estos informes,
podemos comprobar las áreas en las que se necesitan pruebas adicionales,
y el enfoque modular ayuda a centrar a los desarrolladores
en las pruebas importantes en primer lugar.

Además,
la nueva estructura de archivos habla por sí sola.
Los archivos de prueba están junto a los archivos normales,
lo que ofrece a los desarrolladores una forma sencilla de comprobar
si faltan pruebas y ampliarlas.

Discusión continua

Si algún caso requiere una de las características que le faltan a pytest,
cualquier desarrollador puede iniciar una discusión
para validar si es necesaria una nueva característica
o si las herramientas actuales pueden manejar el caso.

Estamos abiertos a discusiones y mejoras con todo el equipo,
dando prioridad a la “testabilidad” y la legibilidad.
Cualquier componente debe ser fácil de probar,
y cualquier prueba debe ser altamente legible.
Incorporamos una solución declarativa (decoradores explícitos en el arranque)
y descriptiva (las pruebas se dividen por secciones Arrange, Act y Assert)
para la

Resultados

Nuestro framework de pruebas permite a los desarrolladores comprobar
si un usuario puede crear una nueva organización
(colección de grupos o proyectos a evaluar)
en nuestra plataforma:

Nueva organización

Ellos también pueden probar si un archivo fue subido a Amazon S3:

Subir archivo a S3

Fue un cambio sorprendente
porque la cantidad de WTFs/minuto que podían generar las pruebas antiguas
era considerable,
más aún cuando había que mantener las pruebas
y algunos mocks podían ocultar posibles errores:

AsyncMock

Así,
conseguimos un código muy sencillo y legible
con nuestro framework de pruebas.
Los desarrolladores han apreciado mucho la nueva experiencia de pruebas
y han comentado con frecuencia la facilidad,
rapidez y confianza con que ejecutan las nuevas pruebas.
Esa buena experiencia reduce progresivamente el problema
de las “assertion-free tests
y motiva a los desarrolladores a escribir pruebas importantes.
Incluso dedicamos algunas semanas a migrar las pruebas antiguas al nuevo marco
al estilo hackathon,
dando prioridad a la calidad de nuestro código
para ser más rápidos después.

Nota:
V. Khorikov en Unit Testing: Principles, Practices, and Patterns
llamó “assertion-free tests” (pruebas sin aserciones)
a aquellas pruebas que no verifican nada.
Incluso con aserciones al final,
una prueba podría afirmar cosas que no son significativas
(p. ej., una función fue llamada n veces
en lugar de consultar una nueva entidad añadida directamente
desde la base de datos).

Una práctica de pruebas continuas
es el primer nivel de cualquier estrategia de seguridad.

En este blog post,
compartí nuestros aprendizajes
en torno a la construcción de un pytest wrapper para estandarización
y sus enormes beneficios.
No olvides que reforzar la estandarización,
reducir las características a las esenciales,
simular tus servicios externos solamente,
y estar abierto a las discusiones del equipo y a la retroalimentación
puede mejorar tu cultura de pruebas.
Cuanto más fácil de probar y legible sea tu código,
más rápido podrás ofrecer nuevo valor
(y más confianza tendrás en tus lanzamientos de producción).

Agradecimientos especiales

  • D. Salazar,
    por apoyar, discutir e implementar el núcleo de esta solución conmigo.

  • D. Betancur y J. Restrepo,
    por adjuntar y priorizar el desarrollo del framework de pruebas
    en el roadmap.

  • El equipo de desarrollo,
    por usar el framework de pruebas,
    dar retroalimentación significativa
    y contribuir a su mantenimiento y crecimiento.

*** This is a Security Bloggers Network syndicated blog from Fluid Attacks RSS Feed authored by Juan Díaz. Read the original post at: https://fluidattacks.com/es/blog/creando-framework-para-pruebas-en-python/