¿Cómo funcionan las herramientas de cobertura de código, como Istanbul for Node.js, para las pruebas de software?

Para realizar algunos experimentos simples, descargue Esprima, Escodegen y una copia en bruto de instrumenter.js del GitHub de Estambul.

Crear un nuevo archivo HTML; si los pones todos en la misma carpeta:

Abra esto en su navegador y abra la consola (F12).

>> var someJS = “var i = 0; if (i> = 0) {console.log (‘hi’)}”
>> var ast = esprima.parse (someJS)
>> ast
Object {type: “Program”, body: Array [2], sourceType: “script”}
>> ast.body [0]
Object {type: “VariableDeclaration”, declaraciones: Array [1], kind: “var”}
>> ast.body [1]
Objeto {tipo: “IfStatement”, prueba: Objeto, consecuente: Objeto, alternativo: nulo}
>> ast.body [0] .declaraciones [0]
Objeto {tipo: “VariableDeclarator”, id: Object, init: Object}
>> ast.body [0] .declarations [0] .id.name
“yo”
>> ast.body [0] .declaraciones [0] .init
Objeto {tipo: “Literal”, valor: 0, sin formato: “0”}
>> ast.body [0] .declarations [0] .init.value = 1

Entonces, Esprima toma un fragmento de JavaScript, como una cadena, y genera un árbol de sintaxis abstracta, una estructura de datos que representa el programa, que podemos navegar fácilmente, o incluso modificar. Escodegen hace la transformación inversa:

>> escodegen.generate (ast)
“var i = 1;
si (i> = 0) {
console.log (‘hola’);
} ”

Entonces, ¿qué hace instrumenter.js? Bueno, puedes leer el código tú mismo; pero aquí hay un ejemplo:

>> var inst = nuevo Instrumentador ()
>> inst.instrumentSync (someJS)

var __cov_1 = (Función (‘devolver esto’)) ();
si (! __ cov_1 .__ cobertura__) {__cov_1 .__ cobertura__ = {}; }
__cov_1 = __cov_1 .__ cobertura__;
if (! (__ cov_1 [‘1457994561008.js’])) {
__cov_1 [‘1457994561008.js’] = {“ruta”: “1457994561008.js”, “s”: {“1”: 0, “2”: 0, “3”: 0}, “b”: {” 1 “: [0,0]},” f “: {},” fnMap “: {},” StatementMap “: {” 1 “: {” start “: {” line “: 1,” column “: 0 }, “fin”: {“línea”: 1, “columna”: 10}}, “2”: {“inicio”: {“línea”: 1, “columna”: 11}, “fin”: {” línea “: 1,” columna “: 44}},” 3 “: {” inicio “: {” línea “: 1,” columna “: 25},” final “: {” línea “: 1,” columna ” : 43}}}, “branchMap”: {“1”: {“line”: 1, “type”: “if”, “ubicaciones”: [{“start”: {“line”: 1, “column” : 11}, “fin”: {“línea”: 1, “columna”: 11}}, {“inicio”: {“línea”: 1, “columna”: 11}, “fin”: {“línea” : 1, “columna”: 11}}]}}};
}
__cov_1 = __cov_1 [‘1457994561008.js’];
__cov_1.s [‘1’] ++; var i = 0; __ cov_1.s [‘2’] ++; if (i> = 0) {__ cov_1.b [‘1’] [0] ++; __ cov_1 .s [‘3’] ++; console.log (‘hi’);} else {__ cov_1.b [‘1’] [1] ++;}

Simplemente cambia el código a otro código, que contiene un objeto de contador y declaraciones de incremento en todas las ramas posibles (también puede notar que donde no había else , agregó uno, para que pueda rastrear cuando no se cumplió la condición). Parece que el algoritmo de qué ignorar está completamente contenido dentro de ese archivo instrumenter.js; también hay una parte que reconoce sugerencias de “no rastrear” (en forma de un comentario como /* istanbul ignore next */ ), que ignora la rama de la AST que sigue.

Uf, eso fue mucho para lo que habría sido una macro Lisp de 3 líneas 😀