mobile menu icon

Usando conjuntamente ApprovalsJs y StrykerJS en WebStorm

Publicado por Fran Reyes & Manuel Rivero el 06/06/2025

Legacy Code, Approval Testing, Testing, Mutation Testing


Introducción.

En un post anterior mostramos como integrar ApprovalsJS con WebStorm.

Para poder aplicar la técnica de Golden Master necesitamos generar nuestro golden master mediante un proceso de muestreo (sampling)[1].

Una vez tenemos escritos los tests aplicando golden master, (o approval testing que como dijimos en el post anterior, facilita la aplicación de la técnica de Golden Master) debemos evaluar lo bien que estos tests protegen el comportamiento existente contra posibles regresiones.

Para ello usaremos herramientas de cobertura y de mutation testing[2] que son capaces de detectar posibles debilidades de nuestros tests que nos lleven a refinarlos hasta que consigamos proteger el comportamiento contra regresiones de forma satisfactoria (el significado de “de forma satisfactoria” dependerá tanto del comportamiento como del tipo de aplicación en cuestión).

La siguiente figura resume este proceso de refinamiento:

Refining golden master tests.
Refinando los tests de golden master (del material del curso Cambiando Legacy).

Por tanto, es muy recomendable usar herramientas de approval testing y mutation testing de manera conjunta.

En el caso que se aborda en este post necesitábamos aplicar estas técnicas para caracterizar un código escrito en TypeScript.

Como ya describimos en un post anterior usamos ApprovalsJS para escribir nuestros tests de golden master.

Para mutation testing en TypeScript la herramienta que más nos gusta usar es StrykerJS. En este post, veremos cómo configurar estas dos herramientas para que trabajen juntas de manera eficiente, dentro de WebStorm.

El problema.

Como ya explicamos, en un approval test cada vez que se produce una diferencia entre el resultado aprobado (esperado) y el obtenido durante el test, ApprovalsJS lanza una herramienta de comparación visual para que el desarrollador pueda evaluar más fácilmente la discrepancia entre los dos resultados[3].

Esto, sin embargo, se vuelve un problema cuando usamos la técnica de mutation testing que se basa en introducir mutaciones (básicamente regresiones) deliberadas en el código fuente para comprobar si los tests fallan como se espera.

Una herramienta de mutation testing generará un montón de copias del código introduciendo una mutación en cada copia (el mutante), y luego ejecutará nuestros tests contra cada una de esas copias.

how mutation testing tools work.
Funcionamiento de una herramienta de mutation testing (del material del curso Cambiando Legacy).

Si los tests fallan al ejecutarlos contra un mutante es que nos protegen contra ese tipo de regresión, si no fallan puede que hayamos descubierto una debilidad en nuestros tests[4].

Cuando usamos una herramienta de mutation testing, si nuestros tests no son terribles de partida, para la mayoría de los mutantes nuestros tests deberían fallar.

El problema es que, si resulta que el test que falla es un approval test lanzará la herramienta de diff que hayamos configurada y se parará la ejecución hasta que interactuemos con dicha herramienta. Por tanto, el flujo de un approval test de lanzar un diff tool cuando el test falla no es compatible con mutation testing y se convierte en un obstáculo.

Hay que decir que este posible problema ya esta resuelto de entrada cuando hemos usado approval testing junto con mutation testing en Java y .Net, pero, por desgracia, no es así cuando usamos ApprovalsJS y StrykerJS con WebStorm.

Cuando ejecutamos una herramienta de mutation testing debemos evitar que se lance la herramienta de diff cada vez que un test de approval falle nos interesa para que el proceso pueda ejecutarse sin intervención humana.

Nuestra solución.

Para resolver esta incompatibilidad entre mutation testing y approval testing necesitaremos informar de alguna manera al Reporter del contexto en que se están ejecutando los tests, básicamente, saber si estamos usando mutation testing o no.

Consultando en el código fuente de ApprovalsJS la clase GenericDiffReporterBase de la que extendimos para crear nuestro reporter para WebStorm, WebStormReporter, vemos que tiene un método público isReporterAvailable(): boolean que se usa como clausula de guarda en el método canReportOn(fileName: string): boolean de la interfaz Reporter[5], si isReporterAvailable devuelve false canReportOn también devolverá false, y eso evitará que abra la ventana de la herramienta de diff de WebStorm.

Sabiendo esto sobreescribimos el método isReporterAvailable(): boolean en WebStormReporter:

import {GenericDiffReporterBase} from "approvals/lib/Reporting/GenericDiffReporterBase";
import {searchForExecutable} from "approvals/lib/AUtils";
import {Config} from "approvals/lib/config";
class WebStormReporter extends GenericDiffReporterBase {
constructor() {
super("webstorm-reporter");
this.exePath = searchForExecutable("webstorm");
}
isReporterAvailable(): boolean {
if(process.env.MUTATION_TESTING && process.env.MUTATION_TESTING === "true") {
return false;
}
return super.isReporterAvailable();
}
public report(
approved: string,
received: string,
options: Partial<Config> = {},
): void {
options.cmdArgs = ["diff", received, approved];
return super.report(approved, received, options);
}
}

Para que funcione como queremos sólo nos faltaría setear la variable de entorno de node MUTATION_TESTING a true cada vez que ejecutamos StrykerJS. Esto se puede conseguir utilizando la propiedad "command" del objeto "commandRunner"’ en el fichero stryker.conf.json`, que configura StrykerJS, de la siguiente manera:

{
"checkers": ["typescript"],
"tsconfigFile": "stryker-tsconfig.json",
"commandRunner": { "command": "export MUTATION_TESTING=true && npm test" }
}

Con esta modificación de la configuración de StrykerJS y con el cambio en WebStormReporter que mostramos más arriba se consigue que la herramienta de diff nunca se lance mientras se ejecutan los tests de mutación.

La variable de entorno MUTATION_TESTING sólo existirá mientras dure la ejecución de los tests de mutación, de forma que, si, en cualquier momento, ejecutamos tests que no sean de mutación recuperamos el comportamiento de lanzar la herramienta de diff configurada cada vez que un test de approval falle.

Conclusión.

Utilizando tests de mutación con StrykerJS en WebStorm para intentar refinar unos tests de golden master que usaban ApprovalsJS nos encontramos la sorpresa de que la herramienta de diff se lanzaba cada vez que los tests fallaba, lo cuál ocurría casi para cada uno de los mutantes generados haciendo que en la práctica la ejecución de los tests de mutación dejara de ser automática.

Para resolverlo tuvimos que consultar el código fuente de ApprovalsJS para saber como modificar nuestra implementación de Reporter para WebStorm, y modificar la configuración de StrykerJS para introducir una variable de entorno de Node que informara a nuestro Reporter de que los tests de approval se estaban ejecutando en el contexto de una ejecución de los tests de mutación.

Esperamos que esta solución les pueda servir para no caer en el mismo problema en que caímos nosotros.

Agradecimientos.

Nos gustaría agradecer a las empresas que hasta ahora han confiado en nosotros para ayudar a sus equipos a mejorar cómo trabajan con su código legacy. Ya hemos dado cuatro ediciones del curso Cambiando Legacy que han tenido muy buen feedback y nos han permitido refinar su contenido.

Por último, también nos gustaría darle las gracias a Erik Mclean por la foto del post.

Notas.

[1] Existen diferentes estrategias de sampling para generar nuestro golden master (input y output). Las principales estrategias de sampling son:

a. Faking input & recording output.

b. Generating random input & recording output.

c. Recording production input & output.

Profundizamos en el refinamiento en nuestro curso Cambiando Legacy.

[2] En nuestro blog puedes encontrar otros posts interesantes sobre mutation testing.

[3] En Integrando ApprovalsJS con WebStorm explicamos cómo hicimos para que ApprovalJs usará la herramienta de diff de WebStorm.

[4] No siempre es así, hay mutantes supervivientes (para los que nuestros tests no fallan) que no son relevantes para mejorar los tests, sino que podrían ser debidos, o bien, a código muerto, o bien, a código innecesario. Para profundizar en esta idea lee nuestro post Mutantes relevantes.

[5] Ver el código de isReporterAvailable y canReportOn en la clase GenericDiffReporterBase, y la interface Reporter.

Volver a posts