Desplazamiento de las pruebas a la izquierda con pruebas unitarias

Las pruebas ayudan a garantizar que el código funciona según lo previsto, pero el tiempo y el esfuerzo para compilar pruebas tardan tiempo en otras tareas, como el desarrollo de características. Con este costo, es importante extraer el valor máximo de las pruebas. En este artículo se describen los principios de prueba de DevOps, centrándose en el valor de las pruebas unitarias y en una estrategia de prueba de desplazamiento a la izquierda.

Los evaluadores dedicados se usan para escribir la mayoría de las pruebas y muchos desarrolladores de productos no han aprendido a escribir pruebas unitarias. Escribir pruebas puede parecer demasiado difícil o como demasiado trabajo. Puede haber dudas sobre si una estrategia de pruebas unitarias funciona, malas experiencias con pruebas unitarias mal escritas o miedo a que las pruebas unitarias reemplacen las pruebas funcionales.

Gráfico que describe los argumentos sobre la adopción de pruebas unitarias.

Para implementar una estrategia de prueba de DevOps, sea pragmático y céntrese en la creación de impulso. Aunque puede insistir en pruebas unitarias para código nuevo o código existente que se puede refactorizar de forma limpia, podría tener sentido que un código base heredado permita alguna dependencia. Si partes significativas del código del producto usan SQL, lo que permite que las pruebas unitarias tomen dependencias en el proveedor de recursos de SQL en lugar de simular esa capa podrían ser un enfoque a corto plazo para el progreso.

A medida que las organizaciones de DevOps maduran, resulta más fácil para el liderazgo mejorar los procesos. Aunque puede haber cierta resistencia al cambio, las organizaciones ágiles valoran los cambios que pagan claramente dividendos. Debe ser fácil vender la visión de ejecuciones de pruebas más rápidas con menos errores, ya que significa más tiempo invertir en generar un nuevo valor a través del desarrollo de características.

Taxonomía de prueba de DevOps

Definir una taxonomía de prueba es un aspecto importante del proceso de prueba de DevOps. Una taxonomía de prueba de DevOps clasifica las pruebas individuales por sus dependencias y el tiempo que tardan en ejecutarse. Los desarrolladores deben comprender los tipos correctos de pruebas que se usarán en distintos escenarios y qué pruebas requieren diferentes partes del proceso. La mayoría de las organizaciones clasifican las pruebas en cuatro niveles:

  • Las pruebas L0 y L1 son pruebas unitarias o pruebas que dependen del código del ensamblado sometido a prueba y nada más. L0 es una amplia clase de pruebas unitarias rápidas y en memoria.
  • L2 son pruebas funcionales que pueden requerir el ensamblado más otras dependencias, como SQL o el sistema de archivos.
  • Las pruebas funcionales L3 se ejecutan en implementaciones de servicio que se pueden probar. Esta categoría de prueba requiere una implementación de servicio, pero puede usar códigos auxiliares para las dependencias de servicio clave.
  • Las pruebas L4 son una clase restringida de pruebas de integración que se ejecutan en producción. Las pruebas L4 requieren una implementación completa del producto.

Aunque sería ideal para que todas las pruebas se ejecuten en todo momento, no es factible. Teams puede seleccionar dónde se encuentra el proceso de DevOps para ejecutar cada prueba y usar estrategias de desplazamiento a la izquierda o de desplazamiento a la derecha para mover diferentes tipos de prueba anteriores o posteriores en el proceso.

Por ejemplo, la expectativa podría ser que los desarrolladores siempre ejecuten pruebas L2 antes de confirmar, se produce un error en una solicitud de incorporación de cambios automáticamente si se produce un error en la ejecución de pruebas L3 y la implementación podría bloquearse si se produce un error en las pruebas L4. Las reglas específicas pueden variar de la organización a la organización, pero la aplicación de las expectativas de todos los equipos de una organización mueve a todos los usuarios hacia los mismos objetivos de visión de calidad.

Directrices para pruebas unitarias

Establezca directrices estrictas para las pruebas unitarias L0 y L1. Estas pruebas deben ser muy rápidas y confiables. Por ejemplo, el tiempo medio de ejecución por prueba L0 en un ensamblado debe ser inferior a 60 milisegundos. El tiempo medio de ejecución por prueba L1 en un ensamblado debe ser inferior a 400 milisegundos. Ninguna prueba en este nivel debe superar los 2 segundos.

Un equipo de Microsoft ejecuta más de 60 000 pruebas unitarias en paralelo en menos de seis minutos. Su objetivo es reducir este tiempo a menos de un minuto. El equipo realiza un seguimiento del tiempo de ejecución de pruebas unitarias con herramientas como el siguiente gráfico y archivos de errores en las pruebas que superan el tiempo permitido.

Gráfico que muestra el foco continuo en el tiempo de ejecución de pruebas.

Directrices de pruebas funcionales

Las pruebas funcionales deben ser independientes. El concepto clave de las pruebas L2 es el aislamiento. Las pruebas aisladas correctamente se pueden ejecutar de forma confiable en cualquier secuencia, ya que tienen control total sobre el entorno en el que se ejecutan. El estado debe conocerse al principio de la prueba. Si una prueba creó datos y los dejó en la base de datos, podría dañar la ejecución de otra prueba que se basa en un estado de base de datos diferente.

Las pruebas heredadas que necesitan una identidad de usuario podrían haber llamado proveedores de autenticación externos para obtener la identidad. Esta práctica presenta varios desafíos. La dependencia externa podría no ser confiable o no disponible momentáneamente, lo que podría interrumpir la prueba. Esta práctica también infringe el principio de aislamiento de prueba, ya que una prueba podría cambiar el estado de una identidad, como el permiso, lo que da lugar a un estado predeterminado inesperado para otras pruebas. Considere la posibilidad de evitar estos problemas invirtiendo en el soporte técnico de identidad en el marco de pruebas.

Principios de prueba de DevOps

Para ayudar a realizar la transición de una cartera de pruebas a procesos modernos de DevOps, articula una visión de calidad. Los equipos deben cumplir los siguientes principios de prueba al definir e implementar una estrategia de pruebas de DevOps.

Diagrama que muestra un ejemplo de una visión de calidad y enumera los principios de prueba.

Mayús a la izquierda para probarlo anteriormente

Las pruebas pueden tardar mucho tiempo en ejecutarse. A medida que los proyectos se escalan, los números de prueba y los tipos aumentan considerablemente. Cuando los conjuntos de pruebas crecen hasta tardar horas o días en completarse, pueden empujar más lejos hasta que se ejecuten en el último momento. Las ventajas de calidad del código de las pruebas no se realizan hasta mucho tiempo después de confirmar el código.

Las pruebas de ejecución prolongada también pueden producir errores que consumen mucho tiempo para investigar. Teams puede crear una tolerancia a errores, especialmente al principio de los sprints. Esta tolerancia reduce el valor de las pruebas como información sobre la calidad del código base. Las pruebas de larga duración y de última hora también agregan imprevisibilidad a las expectativas de fin de sprint, ya que se debe pagar una cantidad desconocida de deuda técnica para obtener el código enviado.

El objetivo de cambiar las pruebas a la izquierda es mover la calidad ascendente mediante la realización de tareas de prueba anteriormente en la canalización. A través de una combinación de mejoras de pruebas y procesos, el desplazamiento a la izquierda reduce el tiempo que tardan las pruebas en ejecutarse y el impacto de los errores más adelante en el ciclo. El desplazamiento a la izquierda garantiza que la mayoría de las pruebas se completan antes de que un cambio se combine en la rama principal.

Diagrama que muestra el movimiento a las pruebas de desplazamiento a la izquierda.

Además de cambiar ciertas responsabilidades de prueba a la izquierda para mejorar la calidad del código, los equipos pueden cambiar otros aspectos de prueba a la derecha, o más adelante en el ciclo de DevOps, para mejorar el producto final. Para obtener más información, consulte Mayús derecho a probar en producción.

Escribir pruebas en el nivel más bajo posible

Escribir más pruebas unitarias. Favorece las pruebas con las dependencias externas más pocas y céntrese en ejecutar la mayoría de las pruebas como parte de la compilación. Considere un sistema de compilación paralelo que pueda ejecutar pruebas unitarias para un ensamblado en cuanto se quiten el ensamblado y las pruebas asociadas. No es factible probar todos los aspectos de un servicio en este nivel, pero el principio es usar pruebas unitarias más ligeras si pueden producir los mismos resultados que las pruebas funcionales más pesadas.

Objetivo de la confiabilidad de las pruebas

Una prueba no confiable es costosa de la organización para mantener. Esta prueba funciona directamente con el objetivo de eficiencia de ingeniería haciendo que sea difícil realizar cambios con confianza. Los desarrolladores deben poder realizar cambios en cualquier lugar y obtener rápidamente la confianza de que no se ha roto nada. Mantenga una barra alta para la confiabilidad. Desalenta el uso de pruebas de IU, ya que tienden a ser poco confiables.

Escritura de pruebas funcionales que se pueden ejecutar en cualquier lugar

Las pruebas pueden usar puntos de integración especializados diseñados específicamente para habilitar las pruebas. Una razón para esta práctica es la falta de capacidad de prueba en el propio producto. Desafortunadamente, las pruebas como estas a menudo dependen de conocimientos internos y usan detalles de implementación que no importan desde una perspectiva de prueba funcional. Estas pruebas se limitan a entornos que tienen los secretos y la configuración necesarios para ejecutar las pruebas, que generalmente excluyen las implementaciones de producción. Las pruebas funcionales solo deben usar la API pública del producto.

Diseñar productos para la capacidad de prueba

Las organizaciones en un proceso de DevOps de maduración tienen una vista completa de lo que significa ofrecer un producto de calidad en una cadencia en la nube. Cambiar el equilibrio fuertemente a favor de las pruebas unitarias sobre las pruebas funcionales requiere que los equipos realicen opciones de diseño e implementación que admitan la capacidad de prueba. Hay diferentes ideas sobre lo que constituye código bien diseñado y bien implementado para la capacidad de prueba, al igual que hay diferentes estilos de codificación. El principio es que el diseño de la capacidad de prueba debe convertirse en una parte principal de la discusión sobre el diseño y la calidad del código.

Tratar el código de prueba como código de producto

Al indicar explícitamente que el código de prueba es código de producto, queda claro que la calidad del código de prueba es tan importante para el envío como el del código del producto. Teams debe tratar el código de prueba de la misma manera que tratan el código de producto y aplicar el mismo nivel de cuidado al diseño y la implementación de pruebas y marcos de pruebas. Este esfuerzo es similar a la administración de la configuración y la infraestructura como código. Para completarse, una revisión de código debe tener en cuenta el código de prueba y almacenarlo en la misma barra de calidad que el código del producto.

Uso de la infraestructura de prueba compartida

Reduzca la barra para usar la infraestructura de prueba para generar señales de calidad de confianza. Ver las pruebas como un servicio compartido para todo el equipo. Almacenar el código de prueba unitaria junto con el código del producto y compilarlo con el producto. Las pruebas que se ejecutan como parte del proceso de compilación también se deben ejecutar en herramientas de desarrollo como Azure DevOps. Si las pruebas se pueden ejecutar en todos los entornos desde el desarrollo local hasta la producción, tienen la misma confiabilidad que el código del producto.

Hacer que los propietarios de código sean responsables de las pruebas

El código de prueba debe residir junto al código del producto en un repositorio. Para que el código se pruebe en un límite de componente, inserte la responsabilidad de las pruebas en la persona que escribe el código del componente. No confíe en otros usuarios para probar el componente.

Caso práctico: Desplazamiento a la izquierda con pruebas unitarias

Un equipo de Microsoft decidió reemplazar sus conjuntos de pruebas heredados por pruebas unitarias modernas de DevOps y un proceso de desplazamiento a la izquierda. El equipo ha realizado un seguimiento del progreso en sprints triweekly, como se muestra en el gráfico siguiente. El gráfico cubre sprints 78-120, que representa 42 sprints durante 126 semanas, o aproximadamente dos y medio años de esfuerzo.

El equipo comenzó en 27K pruebas heredadas en sprint 78 y alcanzó cero pruebas heredadas en S120. Un conjunto de pruebas unitarias L0 y L1 reemplazó a la mayoría de las pruebas funcionales antiguas. Las nuevas pruebas L2 reemplazaron algunas de las pruebas y muchas de las pruebas antiguas se eliminaron.

Diagrama que muestra un saldo de cartera de pruebas de ejemplo a lo largo del tiempo.

En un recorrido de software que tarda más de dos años en completarse, hay mucho que aprender del propio proceso. En general, el esfuerzo por rehacer completamente el sistema de pruebas durante dos años fue una inversión masiva. No todos los equipos de características realizaron el trabajo al mismo tiempo. Muchos equipos de toda la organización han invertido tiempo en cada sprint y, en algunos sprints, era la mayoría de lo que hizo el equipo. Aunque es difícil medir el costo del cambio, era un requisito no negociable para los objetivos de calidad y rendimiento del equipo.

Introducción

Al principio, el equipo dejó las pruebas funcionales antiguas, llamadas pruebas TRA, solo. El equipo quería que los desarrolladores compren la idea de escribir pruebas unitarias, especialmente para las nuevas características. El objetivo era facilitar la creación de pruebas L0 y L1. El equipo necesitaba desarrollar esa capacidad en primer lugar y crear impulso.

En el gráfico anterior se muestra el recuento de pruebas unitarias empezando a aumentar al principio, ya que el equipo vio la ventaja de crear pruebas unitarias. Las pruebas unitarias eran más fáciles de mantener, más rápidas de ejecutar y tenían menos errores. Era fácil obtener compatibilidad con la ejecución de todas las pruebas unitarias en el flujo de solicitudes de incorporación de cambios.

El equipo no se centró en escribir nuevas pruebas L2 hasta el sprint 101. Mientras tanto, el recuento de pruebas tra descendió de 27.000 a 14.000 de Sprint 78 a Sprint 101. Las nuevas pruebas unitarias reemplazaron algunas de las pruebas tra, pero muchas simplemente se eliminaron, en función del análisis del equipo de su utilidad.

Las pruebas tra saltaron de 2100 a 3800 en sprint 110 porque se detectaron más pruebas en el árbol de origen y se agregaron al gráfico. Resultó que las pruebas siempre se habían estado ejecutando, pero no se hacía un seguimiento correcto. Esto no era una crisis, pero era importante ser honesto y reasesss según sea necesario.

Obtención más rápida

Una vez que el equipo tenía una señal de integración continua (CI) que era extremadamente rápida y confiable, se convirtió en un indicador de confianza para la calidad del producto. En la captura de pantalla siguiente se muestra la solicitud de incorporación de cambios y la canalización de CI en acción y el tiempo necesario para recorrer varias fases.

Diagrama que muestra la solicitud de incorporación de cambios y la canalización de CI gradual en acción.

La solicitud de incorporación de cambios tarda unos 30 minutos en combinarse, lo que incluye la ejecución de 60 000 pruebas unitarias. La combinación de código a la compilación de CI es de aproximadamente 22 minutos. La primera señal de calidad de CI, SelfTest, viene después de aproximadamente una hora. A continuación, la mayoría del producto se prueba con el cambio propuesto. En un plazo de dos horas desde Merge a SelfHost, se prueba todo el producto y el cambio está listo para entrar en producción.

Uso de métricas

El equipo realiza un seguimiento de un cuadro de mandos como el ejemplo siguiente. En un nivel alto, el cuadro de mandos realiza un seguimiento de dos tipos de métricas: Mantenimiento o deuda y velocidad.

Diagrama que muestra un cuadro de mandos de métricas para realizar un seguimiento del rendimiento de las pruebas.

En el caso de las métricas de mantenimiento del sitio activo, el equipo realiza un seguimiento del tiempo para detectar, el tiempo de mitigación y el número de elementos de reparación que lleva un equipo. Un elemento de reparación es el trabajo que el equipo identifica en una retrospectiva del sitio activo para evitar que se repitan incidentes similares. El cuadro de mandos también realiza un seguimiento de si los equipos están cerrando los elementos de reparación dentro de un período de tiempo razonable.

En el caso de las métricas de mantenimiento de ingeniería, el equipo realiza un seguimiento de los errores activos por desarrollador. Si un equipo tiene más de cinco errores por desarrollador, el equipo debe priorizar la corrección de esos errores antes del nuevo desarrollo de características. El equipo también realiza un seguimiento de los errores antiguos en categorías especiales, como la seguridad.

Las métricas de velocidad de ingeniería miden la velocidad en diferentes partes de la canalización de integración continua y entrega continua (CI/CD). El objetivo general es aumentar la velocidad de la canalización de DevOps: a partir de una idea, la obtención del código en producción y la recepción de datos de los clientes.

Pasos siguientes