Pruebas, Integración y Calidad
Mocha
Jest
Jest (opens in a new tab) es un framework de prueba de código abierto desarrollado por Facebook e integrado en el popular paquete de create-react-app (opens in a new tab).
npm install --save-dev jest
Jest viene con capacidades de built-in mocking y aserción incorporadas. Además, Jest ejecuta sus pruebas simultáneamente en paralelo, lo que proporciona una ejecución de prueba más suave y más rápida.
Jest también proporciona snapshots testing (opens in a new tab).
Mocking
Mocking means creating a fake version of an external or internal service that can stand in for the real one, helping your tests run more quickly and more reliably. When your implementation interacts with an object’s properties, rather than its function or behavior, a mock can be used.
Stubbing
Stubbing, like mocking, means creating a stand-in, but a stub only mocks the behavior, but not the entire object. This is used when your implementation only interacts with a certain behavior of the object.
To give an example: You can stub a database by implementing a simple in-memory structure for storing records. The object under test can then read and write records to the database stub to allow it to execute the test. This could test some behaviour of the object not related to the database and the database stub would be included just to let the test run.
If you instead want to verify that the object under test writes some specific data to the database you will have to mock the database. Your test would then incorporate assertions about what was written to the database mock.
Examples of Mocking and Stubbing
See the code at ast/test/test.mjs (opens in a new tab) in the repo hello-jison for an example of stubbing the console.log
.
Cuando vaya a escribir las pruebas de la práctica podemos intentar una aproximación como esta: ➜ arith2js-solution git:(dependencies) ✗ cat test/data/test1.calc
- Tomamos un objeto como
c = { text: "3! - 1", result: 5 }
con el atributotext
conteniendo la expresión de prueba y el atributoresult
el resultado esperado después de la traducción y evaluación del código - Construimos primero el árbol con
t = p.parse(c.text)
- Generamos el JavaScript con
js = escodegen.generate(t)
- Evaluamos el JavaScript con
result = eval(js)
- Si nuestro traductor es correcto
result
debería ser igualc.result
Suena bien ¿Verdad?
Pero en tal aproximación ¡tenemos un problema! y es que el código JavaScript generado para "3! - 1"
nos han pedido que sea:
➜ arith2js-solution git:(dependencies) ✗ cat test/data/test1.calc
3! - 1
➜ arith2js-solution git:(dependencies) ✗ bin/calc2js.mjs test/data/test1.calc
#!/usr/bin/env node
const factorial = n => (n === 0) ? 1 : n * factorial(n - 1);
console.log(factorial(3) - 1);
y si evaluamos el código resultante:
➜ arith2js-solution git:(dependencies) ✗ node
Welcome to Node.js v16.0.0.
Type ".help" for more information.
> result = eval(`const factorial = n => (n === 0) ? 1 : n * factorial(n - 1);
... console.log(factorial(3) - 1);`)
5
undefined
> result
undefined
¡La variable result
está undefined
!
Esto es así porque la llamada a console.log()
siempre retorna undefined
(no se confunda por el 5
que aparece en stdout
producido por el console.log
. El valor retornado es undefined
)
Así pues una aproximación como esta no funcionaría:
const p = require("../src/transpile.js").parser;
const escodegen = require("escodegen");
require("chai").should();
const Checks = [
{ text: "2+3*2", result: 8 },
{ text: "4-2-1", result: 1 },
];
describe("Testing translator", () => {
for (let c of Checks) {
it(`Test ${c.text} = ${c.result}`, () => {
const t = p.parse(c.text);
const js = escodegen.generate(t);
const result = eval(js);
result.should.equal(c.result);
console.log = oldLog;
});
}
});
No funcionaría porque lo que queda en result
es undefined
y no el resultado de 2+3*2
.
¿Cómo arreglarlo?
¡El patrón de Stubbing al rescate!
Sustituyamos el método log
del objeto console
con nuestra propia función adaptada a nuestras necesidades de testing console.log = x => x;
que retorna el valor del argumento pasado a console.log
. De esta forma podemos acceder al valor de la evaluación de la expresión:
it(`Test ${c.text} = ${c.result}`, () => {
let oldLog = console.log;
console.log = x => x;
const t = p.parse(c.text);
const js = escodegen.generate(t);
const result = eval(js);
result.should.equal(c.result);
console.log = oldLog;
});
}
});
Ahora result
contiene la evaluación de la expresión y las pruebas funcionan.
Cubrimiento /Covering
In computer science, code coverage is a measure used to describe the degree to which the source code of a program is tested by a particular test suite.
A program with high code coverage has been more thoroughly tested and has a lower chance of containing software bugs than a program with low code coverage.
Many different metrics can be used to calculate code coverage; some of the most basic are:
- the percent of program subroutines and
- the percent of program statements called during execution of the test suite.
Ideally your covering must include at least smoke testing and edge cases.
Smoke Testing
- Smoke testing (also confidence testing, sanity testing) is preliminary testing to reveal simple failures
- Smoke testing refers to test cases that cover the most important functionality of a component or system
Edge cases
-
In programming, an edge case typically involves input values that require special handling in an algorithm behind a computer program.
-
As a measure for validating the behavior of computer programs in such cases, unit tests are usually created; they are testing boundary conditions of an algorithm, function or method.
-
A series of edge cases around each "boundary" can be used to give reasonable coverage and confidence using the assumption that if it behaves correctly at the edges, it should behave everywhere else.
-
For example, a function that divides two numbers might be tested using both very large and very small numbers. This assumes that if it works for both ends of the magnitude spectrum, it should work correctly in between.
Tools: nyc / Istanbul
Here is an example of use of nyc
:
➜ hello-compilers-solution git:(master) ✗ jq '.scripts' package.json
{
"test": "npm run build; mocha",
"mmt": "npm run build; ./bin/mmt.js",
"build": "jison src/maxmin-ast.jison src/maxmin.l -o src/maxmin.js",
"cov": "nyc npm run test"
}
To run it:
hello-compilers-solution git:(master) ✗ npm run cov
> hello-compilers@1.0.1 cov
> nyc npm run test
> hello-compilers@1.0.1 test
> npm run build; mocha
> hello-compilers@1.0.1 build
> jison src/maxmin-ast.jison src/maxmin.l -o src/maxmin.js
Testing hello maxmin translator
✔ Test 2@1&3 = 2
✔ Test 2@1@3 = 3
✔ Test 2&1&3 = 1
✔ Test 2&1@3 = 3
✔ Test 2&(1@3) = 2
5 passing (12ms)
--------------|---------|----------|---------|---------|------------------------------------------------------------------------
File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
--------------|---------|----------|---------|---------|------------------------------------------------------------------------
All files | 57.33 | 43.45 | 45.71 | 54.41 |
ast-build.js | 100 | 100 | 100 | 100 |
maxmin.js | 56.74 | 43.45 | 40.62 | 53.73 | ...456-463,469,490-498,511,516,530-545,554-575,582,584,586,608-613,616
--------------|---------|----------|---------|---------|------------------------------------------------------------------------
By default nyc uses Istanbul's text reporter. However, you may specify an alternative reporter. Here is a list of all available reporters, as well as the output it generates (opens in a new tab).
The value html
generates a HTML report you can view in your browse. You can specify more than one reporter
:
npx nyc --reporter=html --reporter=text --report-dir docs mocha
That will produce a web page report like this in the folder docs
(by default the output folder will be named coverage
):
➜ hello-compilers-solution git:(master) ✗ ls -ltr docs
total 328
-rw-r--r-- 1 casianorodriguezleon staff 5394 27 feb 13:49 base.css
-rw-r--r-- 1 casianorodriguezleon staff 2655 27 feb 13:49 block-navigation.js
-rw-r--r-- 1 casianorodriguezleon staff 540 27 feb 13:49 favicon.png
-rw-r--r-- 1 casianorodriguezleon staff 209 27 feb 13:49 sort-arrow-sprite.png
-rw-r--r-- 1 casianorodriguezleon staff 6181 27 feb 13:49 sorter.js
-rw-r--r-- 1 casianorodriguezleon staff 676 27 feb 13:49 prettify.css
-rw-r--r-- 1 casianorodriguezleon staff 17590 27 feb 13:49 prettify.js
-rw-r--r-- 1 casianorodriguezleon staff 5153 27 feb 13:49 index.html
-rw-r--r-- 1 casianorodriguezleon staff 9906 27 feb 13:49 ast-build.js.html
-rw-r--r-- 1 casianorodriguezleon staff 92586 27 feb 13:49 maxmin.js.html
Referencias
- Mas sobre Mocha en estos apuntes
- Mas sobre Jest en estos apuntes
- Stubbing and Mocking with the Sinon Library
- Travis
- Mas en estos apuntes sobre Travis
- Travis en los apuntes del curso 16/17 (opens in a new tab)
- Antiguos Apuntes sobre Pruebas en el Navegador/Browser
- Learning JavaScript Test-Driven Development by Example (opens in a new tab) SitePoint Tutorial
- Mocha y Chai en el navegador. Apuntes del curso 15/16 (opens in a new tab)
- Testing your frontend JavaScript code using mocha, chai, and sinon. Nicolas Perriault (opens in a new tab) 2013 Covering (opens in a new tab) 16/17