Análisis de mutaciones o cómo evaluar las pruebas.

Nunca hay demasiadas pruebas, todos lo saben. Los memes sobre pruebas de unidad e integración ya no son muy divertidos. Pero aún no sabemos si es posible confiar en los resultados de pasar las pruebas y qué porcentaje de cobertura nos permitirá no dejar que los errores entren en producción. Si hay cambios fatales en las pruebas de omisión de código, sin afectar su resultado, entonces la solución se sugiere a sí misma: ¡debe probar las pruebas!



El enfoque para automatizar esta tarea fue el informe de Mark Langovoy en Frontend Conf . El video y el artículo son breves, y las ideas funcionan muy bien; debe tomar nota.


Sobre el orador: Mark Langovoi ( marklangovoi ) trabaja en Yandex en el proyecto Yandex.Tolok . Esta es una plataforma de crowdsourcing para marcar rápidamente grandes cantidades de datos. Los clientes descargan datos que, por ejemplo, deben estar preparados para su uso en algoritmos de aprendizaje automático y establecer un precio, y por otro lado, los ejecutores pueden completar tareas y ganar dinero.

En su tiempo libre, Mark desarrolla los Krasnodar Devodar Developer Days, una de las 19 comunidades de TI cuyos activistas invitamos a Frontend Conf en Moscú.

Prueba


Existen diferentes tipos de pruebas automatizadas.


Durante las pruebas unitarias populares , escribimos pruebas para partes pequeñas (módulos) de una aplicación. Son fáciles de escribir, pero a veces durante la integración con otros módulos pueden no comportarse exactamente como esperábamos.

Para evitar esto, podemos escribir pruebas de integración que probarán el funcionamiento de nuestros módulos juntos.


Son un poco más complicados, por lo que hoy nos centraremos en las pruebas unitarias.

Prueba unitaria


Cualquier proyecto que quiera al menos una estabilidad mínima es escribir pruebas unitarias.

Considera un ejemplo.

class Signal { on(callback) { ... } off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = [ ...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex) ]; } trigger() { ... } } 

Hay una clase de señal: se trata de un emisor de eventos, que tiene un método de activación para suscribirse y un método de desactivación para eliminar una suscripción. Verificamos si la devolución de llamada está contenida en la matriz de suscriptores y luego la eliminamos. Y, por supuesto, hay un método de activación que llamará a devoluciones de llamada firmadas.

Tenemos una prueba simple para este ejemplo que llama a los métodos de encendido y apagado, y luego al disparador, para verificar que la devolución de llamada no se haya llamado después de cancelar la suscripción.

 test('off method should remove listener', () => { const signal = new Signal(); let wasCalled = false; const callback = () => { wasCalled = true; }; signal.on(callback); signal.off(callback); signal.trigger(); expect(wasCalled).toBeFalsy(); }); 

Criterios de evaluación de calidad


¿Cuáles son los criterios para evaluar la calidad de tal prueba?

La cobertura de código es el criterio más popular y conocido que muestra cuánto porcentaje de las líneas de código se ejecutaron cuando se ejecutó la prueba.


Es posible que tenga una cobertura del 70%, 80% o 90% del Código, pero ¿significa esto que cuando recoja la próxima compilación para producción, todo estará bien o algo puede salir mal?

Volvamos a nuestro ejemplo.

El viernes por la noche, estás cansado, estás terminando la próxima función. Y luego te encuentras con este código que escribió tu colega. Algo sobre ti te parecía complicado y aterrador.

  ...this.listeners.slice(0, callbackIndex - 1), ...this.listeners.slice(callbackIndex) 

Decidiste que probablemente solo puedes borrar la matriz:

 class Signal { ... off(callback) { const callbackIndex = this.listeners.indexOf(callback); if (callbackIndex === -1) { return; } this.listeners = []; } ... } 

Me comprometí, ensamblé el proyecto y lo envié a producción. Pruebas aprobadas, ¿por qué no? Y se fue a descansar a un bar.



Pero de repente, a altas horas de la noche, sonó una llamada en el receptor, que todo estaba cayendo, la gente no podía usar el producto y, en general, ¡el negocio malgastaba dinero! Te quemas, te amenazan con el despido.



¿Cómo lidiar con esto? ¿Qué hacer con las pruebas? ¿Cómo atrapar tales primitivos errores estúpidos? ¿Quién probará las pruebas?

Por supuesto, puede contratar un ejército de ingenieros de control de calidad: déjelos sentarse y simplemente aplauda nuestra aplicación.



O contratar la automatización de control de calidad. Pueden atribuirse a las pruebas de escritura: ¿por qué escribir por su cuenta si hay personas especiales para esto?

Pero, de hecho, es costoso, por lo que hoy hablaremos sobre análisis mutacional o pruebas mutacionales.

Prueba de mutación


Esta es una forma de automatizar el proceso de prueba de nuestras pruebas. Su propósito es identificar pruebas ineficaces e incompletas, es decir, esto es pruebas de prueba .

La idea es cambiar piezas de código, ejecutar pruebas en ellas y, si las pruebas no caen, entonces están incompletas.

Los cambios se realizan utilizando ciertas operaciones: mutadores . Reemplazan, por ejemplo, más por menos, se multiplican por división y otras operaciones similares. Los mutadores pueden cambiar fragmentos de código, reemplazar las condiciones mientras que, cero conjuntos en lugar de agregar algún elemento al conjunto.


Como resultado de aplicar mutaciones al código fuente, muta y se convierte en mutante .

Los mutantes se dividen en dos categorías:

  1. Asesinado : aquellos en los que pudimos identificar desviaciones, es decir, en las que se realizó al menos una prueba.
  2. Los sobrevivientes son los que huyeron de nosotros y trajeron el error a producción.

Para evaluar la calidad, existe una métrica MSI (Indicador de puntaje de mutación) : el porcentaje de mutantes muertos y sobrevivientes. Cuanto mayor sea la diferencia entre las pruebas de cobertura de código y MSI, peor será el porcentaje de cobertura de código que refleja la relevancia de nuestras pruebas.

Esto era un poco teórico, y ahora considera cómo se puede usar en JavaScript.

Solución Javascript


Solo hay una herramienta de prueba de mutación en desarrollo activo en JavaScript: Stryker . Este nombre se le dio al instrumento en honor al personaje de X-man William Stryker, el creador de "Arma X" y un luchador con todos los mutantes.



Stryker no es un corredor de pruebas como Karma o Jest; ni es un marco de prueba como Mocha o Jasmine. Este es un marco de prueba de mutaciones que complementa su infraestructura actual.

Sistema de plugins


Stryker es muy flexible, totalmente construido en un sistema de plug-in, la mayoría de los cuales están escritos por desarrolladores de Stryker.


Hay complementos para ejecutar pruebas en Jest, Karma y Mocha. Hay integración con los frameworks Mocha (stryker-mocha-framework) Jasmine (stryker-jasmine) y conjuntos de mutadores listos para JavaScript, TypeScript e incluso para Vue:

  • stryker-javascript-mutator;
  • stryker-typecript;
  • stryker-vue-mutator.

Mutators for React están incluidos en el stryker-javascript-mutator. Además de esto, siempre puedes escribir tus propios mutadores.

Si necesita convertir el código antes de ejecutarlo, puede usar complementos para Webpack, Babel o TypeScript.


Todo esto se configura de manera relativamente simple.

Configuracion


La configuración no es difícil: solo necesita especificar en la configuración JSON qué corredor de prueba (y / o marco de prueba y / o transpilador) usa, así como instalar los complementos apropiados desde npm.

La sencilla utilidad de consola stryker-cli puede hacer todo esto por usted en un modo de preguntas y respuestas. Ella te preguntará qué usas y se configurará a sí misma.

Como funciona


El ciclo de vida es simple y consta de los siguientes pasos:

  • Lectura y análisis de la configuración. Stryker descarga la configuración y la analiza en busca de varios complementos, configuraciones, exclusión de archivos, etc.
  • Descargar complementos de acuerdo con la configuración.
  • Ejecutar pruebas en el código fuente para verificar si las pruebas son relevantes en este momento (de repente, ya están rotas).
  • Si todo está bien, se genera un conjunto de mutantes a partir de los archivos que permitimos mutar.
  • Ejecución de pruebas en mutantes.



Arriba hay un ejemplo de cómo iniciar Stryker:

  • Stryker se pone en marcha;
  • lee una configuración;
  • carga las dependencias necesarias;
  • encuentra archivos que mutarán;
  • ejecuta pruebas en el código fuente;
  • crea 152 mutantes;
  • ejecuta pruebas en 8 subprocesos (en este caso, en función del número de núcleos de CPU).

Este no es un proceso rápido, por lo que es mejor hacerlo en algunos servidores CI / CD.

Después de pasar todas las pruebas, Stryker ofrece un breve informe sobre los archivos con el número de mutantes creados, muertos y sobrevivientes, así como el porcentaje de la proporción de mutantes muertos y sobrevivientes (MSI) y los mutadores que se usaron.

Estos son problemas potenciales que no estaban previstos en nuestras pruebas.

Para resumir


Las pruebas de mutación son útiles e interesantes . Puede encontrar problemas en las primeras etapas de las pruebas y sin la participación de las personas. Reducirá el tiempo para la validación de la solicitud de extracción, por ejemplo, debido al hecho de que los desarrolladores calificados no tendrán que perder tiempo en la validación de la solicitud de extracción, que ya tiene problemas potenciales. O guarde la producción si decide preparar un nuevo lanzamiento el viernes por la noche.

Stryker es una herramienta de prueba de mutación multiproceso flexible. Se está desarrollando activamente, pero hasta ahora húmedo, aún no ha alcanzado la versión principal. Por ejemplo, durante la preparación de este informe, sus desarrolladores finalmente hicieron posible en el complemento que Babel especificara el archivo de configuración y reparó la integración de Jest. Este es un proyecto OpenSource que se puede ayudar a desarrollar.

FAQ
- ¿Cómo probar las pruebas de mutación? Seguramente, también hay un error. En el primer ejemplo de prueba unitaria, la cobertura fue del 90%. Parece que todo está bien, pero aún así los casos se escaparon cuando todo cayó y se incendió. En consecuencia, ¿por qué debería haber una sensación de que todo está bien después de cubrir estas pruebas mutacionales?

- No digo que las pruebas de mutación sean una bala de plata y lo curarán todo. Naturalmente, puede haber algunos casos locos límite o la ausencia de algún tipo de mutador. En primer lugar, los errores típicos se detectan fácilmente. Por ejemplo, usted verifica la edad, lo establece en <18 (era necesario <=), y en la prueba olvidó hacer una verificación de casos en el borde. Hiciste otra comparación con el mutador y, como resultado, la prueba cayó (o no cayó), y entiendes que todo está bien o todo está mal. Tales cosas son atrapadas rápidamente. Esta es una manera de simplemente agregar pruebas correctamente, encontrar puntos faltantes.

- ¿A menudo tienes una situación de "aturdido y abandonado"? Creo que esto no es cierto.

- No, pero creo que en muchos proyectos existen tales cosas. Naturalmente, esto no es cierto. Muchas personas piensan que la cobertura del Código ayuda a verificar todo, puede irse con seguridad y no preocuparse, pero esto no es así.

- Diré de inmediato cuál es el problema. Tenemos muchos tipos de reductores y otras cosas que probamos mutacionalmente, y hay muchos de ellos. Todo esto crece, y resulta que para cada solicitud de extracción, se inicia la prueba de mutación, lo que lleva mucho tiempo. ¿Es posible ejecutar solo lo que ha cambiado?

"Creo que puedes configurarlo tú mismo". Por ejemplo, del lado del desarrollador, cuando empuja, confirma, puede crear un complemento con pelusa que solo ejecutará archivos que hayan cambiado. En CI / CD, esto también es posible. En nuestro caso, el proyecto es muy grande y antiguo, y practicamos pruebas puntuales. No verificamos todo, porque llevará una semana, habrá cientos de miles de mutaciones. Yo recomendaría hacer verificaciones puntuales u organizar un proceso de inicio selectivo yo mismo. No he visto una herramienta lista para tal integración.

- ¿Está garantizada la integridad de todas las mutaciones posibles para un código en particular? Si no, ¿cómo se seleccionan exactamente las mutaciones?

- Personalmente no lo comprobé, pero tampoco encontré ningún problema con esto. Stryker debe generar todas las mutaciones posibles en el mismo fragmento de código.

- Quiero preguntar sobre instantáneas. Mi prueba unitaria prueba tanto la lógica como el diseño del componente de reacción de instantánea. Naturalmente, si cambio algún diseño lógico, mi diseño cambiará allí mismo. Este es el comportamiento esperado, ¿no?

- Sí, ese es su significado, que actualice manualmente las instantáneas.

- ¿Entonces ignoras instantáneas en este informe?

- Lo más probable es que las instantáneas deban actualizarse por adelantado y luego ejecutar pruebas de mutación, de lo contrario habrá mucha basura de Stryker.

- Pregunta sobre servidores CI. Para pruebas unitarias simples, hay reporteros, bajo GitLab, para cualquier cosa que desee, que muestran el porcentaje de pruebas exitosas, y puede configurar si falla o no falla. ¿Qué hay de Stryker? Simplemente muestra la tableta en la consola, pero ¿qué puedo hacer a continuación?

- Tienen HTML-reporter, puedes hacer tus propios reporteros: todo es flexiblemente personalizable. Quizás haya algunas herramientas específicas, pero como todavía estamos haciendo pruebas de mutación puntual, no encontré integraciones específicas con TeamCity y herramientas de CI / CD similares.

- ¿Cuánto aumentan las pruebas de mutación el soporte para las pruebas que tiene en general? Es decir, las pruebas son difíciles, y las pruebas deben reescribirse cuando se reescribe el código, etc. A veces es más fácil reescribir el código que las pruebas. Y aquí también tengo pruebas mutacionales. ¿Qué tan caro es para un negocio?

- Primero, probablemente corregiré que reescribir el código por el bien de las pruebas es incorrecto. El código debería ser fácil de probar. En cuanto a lo que debe completarse, nuevamente es importante para la empresa que las pruebas sean lo más completas y efectivas posible. Si no están completos, esto significa que puede ocurrir un error que traerá pérdidas. Naturalmente, puede probar solo las partes más importantes para el negocio.

"Aún así, cuánto más caro se vuelve cuando aparecen las pruebas de mutación, que si no estuvieran allí".

- Es tanto como malas pruebas ahora. Si las pruebas están mal escritas ahora, tendrá que agregar mucho. Las pruebas de mutación encontrarán casos que no están cubiertos por las pruebas.

- En la diapositiva con los resultados de la verificación Stryker, hay muchos vorings, son críticos o no críticos. ¿Cómo manejar los falsos positivos?

- La pregunta sutil es qué se considera falso. Les pregunté a los muchachos de nuestro equipo qué cosas interesantes les sucedieron. Hubo un ejemplo sobre el texto de error. Stryker informó que las pruebas no respondieron al cambio del texto de error. Parece ser una jamba, pero menor.

- ¿Entonces ve tales errores y omite los no críticos en modo manual?

"Tenemos un control puntual, así que sí".

Tengo una pregunta práctica. Cuando implementaste esto, ¿qué porcentaje de las pruebas reprobaste?

- No lo implementamos en todo el proyecto, pero hubo problemas menores en el nuevo proyecto. Por lo tanto, no puedo decir los números exactos, pero en general, el enfoque definitivamente ha mejorado la situación.

Vea otras actuaciones frontales igualmente útiles en nuestro canal de youtube , todos los informes temáticos de todas nuestras conferencias llegan gradualmente. O suscríbase al boletín y lo mantendremos informado de todos los nuevos materiales y noticias de futuras conferencias.

Source: https://habr.com/ru/post/es421141/


All Articles