Creando un framework para pruebas en Python

Cómo mejoramos nuestras pruebas al estandarizarlas

Blog Creando un framework para pruebas en Python

| 6 min de lectura

Contáctanos

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.

Empieza ya con la solución DevSecOps de Fluid Attacks

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.

Suscríbete a nuestro blog

Recibe el boletín semanal de Fluid Attacks.

Blog posts recomendados

Quizá te interesen los siguientes posts similares.

Foto por Logan Weaver en Unsplash

Introducción a la ciberseguridad del sector de la aviación

Foto por Maxim Hopman en Unsplash

¿Por qué calcular riesgos de ciberseguridad con nuestra métrica CVSSF?

Foto por Jukan Tateisi en Unsplash

Nuestra nueva arquitectura de pruebas para el desarrollo de software

Foto por Clay Banks en Unsplash

Protegiendo tus TPV de las ciberamenazas

Foto por Charles Etoroma en Unsplash

Los siete ciberataques más exitosos contra esta industria

Foto por Anima Visual en Unsplash

Retos, amenazas y buenas prácticas para los comerciantes

Foto por photo nic en Unsplash

Sé más seguro aumentando la confianza en tu software

Inicia tu prueba gratuita de 21 días

Descubre las ventajas de nuestra solución Hacking Continuo, de la cual ya disfrutan cientos de organizaciones.

Inicia tu prueba gratuita de 21 días
Fluid Logo Footer

Hackeando software durante más de 20 años

Fluid Attacks analiza aplicaciones y otros sistemas, abarcando todas las fases de desarrollo de software. Nuestro equipo ayuda a los clientes a identificar y gestionar rápidamente las vulnerabilidades para reducir el riesgo de ciberincidentes y desplegar tecnología segura.

Copyright © 0 Fluid Attacks. We hack your software. Todos los derechos reservados.