En nuestra serie continua DevOps para ganar, hemos estado documentando nuestro recorrido de transformación DevOps. En el Capítulo 1, presentamos el Triángulo DevOps: herramientas, arquitectura y personas en el desarrollo de productos, donde analizamos los elementos fundamentales que impulsan el éxito de DevOps. En el Capítulo 2, exploramos Los primeros pasos en nuestra transformación DevOps, compartiendo cómo sentamos las bases para cambios significativos.
Ahora, en el Capítulo 3, abordamos el siguiente gran desafío con el que nos encontramos: Diseño para la capacidad de prueba. Tras mejorar la estructura de nuestro equipo, perfeccionar nuestra definición de hecho e implementar el análisis de código automatizado, nos dimos cuenta de que el principal cuello de botella en nuestra cadena de procesos se había trasladado al diseño de software, específicamente a la capacidad de prueba. El trabajo se acumulaba en la fase de pruebas, lo que nos impedía lograr ciclos de lanzamiento más rápidos. Este capítulo profundiza en cómo superamos este desafío y mejoramos nuestra capacidad para ofrecer software de alta calidad de forma eficiente.
El cuello de botella de las pruebas
La razón principal de este retraso fue que las pruebas eran predominantemente manuales, y se centraban en pruebas de interfaz de usuario y flujos de trabajo de los usuarios. Dado que la interfaz de usuario solía ser uno de los últimos elementos en completarse, había poca o ninguna automatización implementada, y las pruebas manuales se convirtieron en el único enfoque viable. Los casos de prueba de interfaz de usuario automatizados (por ejemplo, pruebas de Selenium) normalmente se escribían después de que finalizaban las pruebas manuales, principalmente con fines de regresión.
Esta dependencia de las pruebas manuales provocó:
- Retrasos significativos en las versiones de software.
- La necesidad de realizar múltiples implementaciones para permitir pruebas paralelas por parte de más de un miembro del equipo de QA.
Esto puso de relieve la importancia de hacer que el código fuera más fácil de probar para optimizar el flujo de valor. Sin embargo, lograr esto resultó ser más difícil de lo que habíamos previsto.
Priorización de pruebas de API y pruebas unitarias
Para abordar este desafío, nos centramos en dos áreas principales:
- Pruebas unitarias: Garantizar que los componentes individuales se pudieran probar de forma aislada.
- Pruebas de API: Reducir la dependencia de las pruebas basadas en la interfaz de usuario y las dependencias de bases de datos.
Pruebas de API: anticipación para obtener comentarios más rápidos
Redefinimos nuestra estrategia de pruebas de API con estas mejoras clave:
- APIs bien definidas: Las APIs se diseñaron para ser sencillas, estar bien documentadas y estar disponibles desde las primeras etapas del desarrollo.
- Evitar la base de datos como punto de integración: Depender de bases de datos para la integración creó dependencias que ralentizaron las pruebas. En su lugar, avanzamos hacia la integración basada en API utilizando REST sobre HTTP y GraphQL. Esto minimizó el tiempo de configuración de la base de datos y mejoró la automatización de las pruebas.
Este cambio redujo significativamente los retrasos, lo que permitió una automatización más rápida y pruebas en las primeras etapas, lo que demuestra que centrarse en diseños orientados a API mejoró tanto la capacidad de prueba como la eficiencia.
Pruebas unitarias: un cambio de mentalidad
Inicialmente, nuestros esfuerzos de pruebas unitarias condujeron a aumentos rápidos en la cobertura de las pruebas, pero identificamos rápidamente problemas:
- Algunas pruebas eran superficiales y se escribieron solo para cumplir con los objetivos de cobertura de código.
- Carecían de una validación significativa de la funcionalidad, los casos límite y los escenarios de error.
Para contrarrestar esto, hicimos hincapié en lo siguiente:
- Formar a los desarrolladores en la redacción de pruebas valiosas.
- Refactorización del código para mejorar la capacidad de prueba.
Retos en la redacción de código comprobable
El mayor obstáculo para realizar pruebas unitarias eficaces fue la falta de capacidad de prueba en el propio código. Entre los problemas principales se encontraban:
- Funciones grandes y monolíticas.
- Componentes fuertemente acoplados.
- Deficiente separación de intereses.
- Abstracción insuficiente.
Estos problemas dificultaban el aislamiento y la evaluación eficaz de las unidades individuales. Para solucionarlo, llevamos a cabo lo siguiente:
- Directrices proporcionadas: Compartimos las mejores prácticas sobre:
- Selección de funciones para las pruebas unitarias.
- Refactorización del código para mejorar la capacidad de prueba.
- Diseño y uso de simulaciones para facilitar las pruebas.
- Enfoque en mejoras incrementales: Se alentó a los desarrolladores a realizar cambios pequeños y significativos para mejorar la capacidad de prueba con el tiempo.
Progreso lento pero constante
A pesar de estos esfuerzos, el progreso fue lento, especialmente en el caso de los productos heredados. La arquitectura existente limitaba el número de pruebas unitarias que podíamos escribir. Sin embargo, estas acciones no solo tenían como objetivo mejorar el código base actual, sino que eran una inversión en el futuro:
- Mejorar las habilidades de los desarrolladores en el diseño de código comprobable garantizó que los futuros proyectos no sufrieran los mismos problemas.
- Las mejoras incrementales evitaron interrupciones al tiempo que aumentaron de forma constante la cobertura de las pruebas automatizadas.
- Un cambio de mentalidad ayudó a los equipos a ver la comprobabilidad no como una carga, sino como una necesidad para el éxito sostenible de DevOps.
Para los equipos y organizaciones que se embarcan en una transformación DevOps, la comprobabilidad es un área de enfoque fundamental. Ayuda a aliviar los cuellos de botella en la fase de desarrollo. Sin embargo, es importante gestionar las expectativas: es posible que no se obtengan resultados inmediatos, especialmente cuando se trabaja con grandes bases de código heredadas. Refactorizar dicho código sin suficientes pruebas unitarias y de regresión supone un gran desafío.
Lecciones de The Pragmatic Programmer
Para aquellos escenarios en los que empezar con una nueva base de código no es factible, The Pragmatic Programmer: Your Journey to Mastery, de Andy Hunt y David Thomas ofrece orientación práctica:
- Apunte a cambios incrementales: céntrese en refactorizar partes pequeñas y manejables de la base de código.
- Desacople los componentes: reduzca las dependencias para que las unidades individuales sean más fáciles de probar.
- Divida las funciones monolíticas: separe las funciones grandes en unidades más pequeñas y específicas.
- Adopte un diseño modular: haga que el código sea más comprobable y fácil de mantener mejorando la modularidad.
El mensaje aquí es claro: una vez abordados los cuellos de botella iniciales, la comprobabilidad debe convertirse en un área de enfoque clave. Esto reducirá las limitaciones en la fase de pruebas y permitirá una entrega más rápida de software de alta calidad.
Mirando hacia el futuro
Nuestro recorrido para mejorar la capacidad de prueba nos enseñó valiosas lecciones sobre el papel del diseño de software a la hora de facilitar transiciones de DevOps fluidas. Al centrarnos en la capacidad de prueba, abordamos los cuellos de botella, mejoramos las habilidades de los desarrolladores y sentamos las bases para el éxito futuro. Aunque los resultados inmediatos puedan ser lentos, los beneficios a largo plazo en términos de calidad, eficiencia y crecimiento del equipo hacen que esta inversión valga la pena.
A medida que continuamos nuestro recorrido en DevOps, medir el éxito se convierte en el siguiente desafío clave. En el capítulo 4, exploraremos cómo establecimos indicadores clave de rendimiento (KPI) y paneles para realizar un seguimiento de nuestro progreso e identificar más áreas de mejora.
¡Manténgase atento mientras analizamos cómo la toma de decisiones basada en datos nos ayudó a perfeccionar nuestros procesos de DevOps y optimizar el rendimiento!
