Escena Viva ja té bateria de proves: unitàries del domini i de la política de permisos, dobles per al rellotge i la xarxa, i integració amb supertest contra la base de dades real. npm test està verd.

Però hi ha dues preguntes sense resposta. La primera: quines parts del codi no ha executat mai cap prova? Pot haver-hi una branca de l'if de potCanviarRol que no es recorre mai, un catch que no es dispara mai, una funció exportada que ningú no crida. Això ho respon la cobertura. La segona és més pràctica: què passa quan la bateria creix? Avui triga un segon; amb dues-centes proves d'integració trigarà cinc minuts, i una prova que falla una de cada vuit vegades convertirà la feina de l'equip en una loteria.

Contingut

  1. Què mesura la cobertura i què no
  2. Instal·lar i llegir c8
  3. Llindars i exclusions
  4. Per què el 100 % no és l'objectiu
  5. On és el risc a Escena Viva
  6. Organitzar els scripts d'npm
  7. Proves ràpides i fiables
  8. Caçar una prova intermitent
  9. Preparar el projecte per a integració contínua
  10. Ganxos de git

Què mesura la cobertura i què no

La cobertura mesura quin percentatge del codi s'ha executat durant les proves, i es descompon en quatre mètriques que sovint es confonen:

Mètrica Què compta Exemple
Línies (lines) Línies de codi executades S'ha executat la línia 42?
Sentències (statements) Sentències executades; una línia en pot tenir diverses const a = 1; const b = 2; en són dues
Branques (branches) Camins de cada decisió: if/else, ?:, &&, ??, case S'ha provat l'if i el camí sense if?
Funcions (functions) Funcions invocades almenys una vegada S'ha cridat mai potCanviarRol?

La que de veritat importa és la de branques. Fixa't en aquest exemple, on el 100 % de línies conviu amb la meitat de les branques sense provar:

function calcularTotal(linies, descompte) {
  const brut = linies.reduce((suma, l) => suma + l.quantitat * l.preuCentims, 0);
  return descompte ? Math.floor(brut * 0.9) : brut;
}

// Una unica prova, sense descompte:
expect(calcularTotal([{ quantitat: 2, preuCentims: 2500 }], false)).to.equal(5000);

Aquesta prova executa totes les línies: 100 % de línies, de sentències i de funcions. I el 50 % de branques, perquè la del descompte no es recorre mai. Si Math.floor(brut * 0.9) estigués mal escrit —posem-hi brut * 0.09—, l'informe continuaria lluint gairebé perfecte. El mateix passa a la nostra política d'autorització:

function potGestionarEsdeveniment(usuari, esdeveniment) {
  if (!usuari || !esdeveniment) return false;
  if (usuari.rol === 'administrador') return true;
  return usuari.rol === 'organitzador' && usuari.salaId === esdeveniment.salaId;
}

Una sola prova amb un administrador executa les dues primeres línies i surt: cobertura de línies alta, cobertura de branques baixa, perquè no s'ha provat l'usuari nul, ni l'organitzador de la sala correcta, ni el de la incorrecta, ni l'assistent. I aquí és exactament on viu la fallada de seguretat.

I el que la cobertura no mesura, que és el més important d'aquesta lliçó: no mesura si has comprovat res (executar una línia i afirmar sobre el seu resultat són coses diferents), no mesura si les teves assercions són correctes (expect(true).to.be.true cobreix igual), no mesura els casos que falten (si vendre() no comprova l'aforament, la cobertura serà del 100 % i la sobrevenda existirà) i no diu res sobre la qualitat del disseny. La cobertura és un detector de forats, no un certificat de qualitat: respon «què m'he deixat?», no «està ben provat?».

Instal·lar i llegir c8

Farem servir c8, que aprofita la cobertura nativa de V8: no instrumenta ni transforma el codi i funciona amb CommonJS i ESM sense configuració. La seva alternativa clàssica és nyc, hereva d'Istanbul, que instrumenta el codi abans d'executar-lo; continua sent perfectament vàlida i el seu format d'informe és el mateix, però c8 és més simple de muntar en un projecte modern.

npm install --save-dev c8
$ npx c8 mocha

  ... 74 passing (3.1s)

------------------------|---------|----------|---------|---------|-------------------
File                    | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s
------------------------|---------|----------|---------|---------|-------------------
All files               |   81.42 |    72.09 |   84.61 |   81.42 |
 src/autoritzacio       |  100.00 |   100.00 |  100.00 |  100.00 |
 src/domini             |   97.11 |    91.30 |   96.55 |   97.11 |
  gestor-vendes.js      |   93.75 |    83.33 |   90.00 |   93.75 | 48-51
 src/serveis            |   64.28 |    51.72 |   70.00 |   64.28 |
  tokens.js             |   72.41 |    55.00 |   75.00 |   72.41 | 88-97,112
  auditoria.js          |   31.03 |    16.66 |   33.33 |   31.03 | 12-38,45-52
 scripts                |    0.00 |     0.00 |    0.00 |    0.00 |
  llavor.js             |    0.00 |     0.00 |    0.00 |    0.00 | 1-96
------------------------|---------|----------|---------|---------|-------------------

Com es llegeix, de dreta a esquerra. Uncovered Line #s és la columna més útil: són les línies que cap prova no ha executat, així que ves-hi directament. Un % Branch molt per sota de % Stmts assenyala funcions amb moltes decisions i pocs escenaris provats: mira tokens.js, amb 72 % de sentències i 55 % de branques, que traduït significa que hem provat el camí feliç dels tokens i gairebé cap dels d'error. src/autoritzacio al 100 % és el fruit de la taula de casos de 09-02, i aquí hi ha la lliçó: les funcions pures es cobreixen senceres amb molt poc esforç. I scripts/llavor.js al 0 % no és un problema, és un script d'utilitat que exclourem.

Per investigar de veritat, l'informe HTML (c8 --reporter=html mocha, que genera coverage/index.html) pinta el codi font acolorit: les línies no executades surten en vermell i les branques parcialment cobertes apareixen marcades amb una I (branca if no presa) o una E (branca else no presa). És la manera més ràpida de veure que un catch sencer no s'ha disparat mai. Afegeix coverage/ al .gitignore: és un artefacte generat, no codi.

Llindars i exclusions

Un informe que ningú no mira no serveix de res. Els llindars converteixen la cobertura en una condició de fallada: si baixa del mínim, l'ordre surt amb codi diferent de zero i CI es posa vermell.

{
  "all": true,
  "include": ["src/**/*.js"],
  "exclude": ["src/config/index.js", "src/servidor.js", "src/db/sequelize.js"],
  "reporter": ["text", "html", "lcov"],
  "lines": 75,
  "branches": 65,
  "functions": 75,
  "check-coverage": true
}

L'opció all mereix un paràgraf. Sense ella, c8 només informa dels fitxers que les proves han carregat: si crees src/serveis/notificacions.js i no escrius cap prova, el fitxer no apareix i la cobertura global no baixa. Amb all: true apareix amb un 0 % rotund i la mitjana cau, que és exactament el que vols que passi. La resta: include limita el mesurament a src/, reporter produeix text per a la consola, HTML per investigar i lcov per al servidor de CI, i check-coverage fa fallar l'execució si no es compleixen els mínims.

Sobre la política de llindars, dues regles, i la segona és la important. Comencen baixos: fixa el llindar lleugerament per sota de la cobertura actual, perquè un llindar inassolible s'ignora i un llindar ignorat no existeix. I no es baixen mai: quan la cobertura pugi al 85 %, apuja el llindar a 82, i quan algú proposi abaixar-lo perquè «aquest lliurament urgent no dona temps a provar», la resposta és no. El llindar és un trinquet que només gira en un sentit; en el moment en què es pot abaixar, deixa de ser una restricció i passa a ser un suggeriment. Una variant útil són els llindars per carpeta, exigint més al codi crític: c8 --include='src/domini/**' --include='src/autoritzacio/**' --lines=95 --branches=90 --check-coverage mocha test/unitat/**/*.test.js.

Excloure fitxers és legítim quan mesurar-los no aporta informació, i un frau quan es fa per maquillar el número.

Exclusions legítimes Per què
migrations/ de sequelize-cli Codi generat que s'executa una vegada; es verifica executant-lo
scripts/llavor.js Eina de desenvolupament, ja exercitada per les proves d'integració
src/config/index.js Lectura de variables d'entorn; s'exercita en arrencar qualsevol prova
src/servidor.js Crida listen i escolta senyals; provar-lo requeriria matar processos
Fitxers de configuració i el mateix test/ Configuració, no lògica

Són il·legítimes, i banderes vermelles en una revisió de codi: excloure src/controladors/ perquè «és difícil de provar» (és on viu la lògica de l'API), afegir /* c8 ignore next */ sobre una branca que sí que es pot provar només perquè el llindar passi, excloure el fitxer que acaba de causar una incidència en producció, o excloure carpetes senceres sense cap comentari que ho justifiqui. Quan facis servir una exclusió en línia, deixa escrit el motiu: /* c8 ignore next 3 -- branca defensiva: nomes passa si el motor canvia el format d'error */.

Per què el 100 % no és l'objectiu

Perseguir el 100 % produeix, de manera sistemàtica, proves pitjors. Aquest és el mecanisme:

// Prova escrita per pujar la cobertura d'auditoria.js del 31 % al 90 %
it('registra a auditoria', async () => {
  await auditoria.registrar('compra', { usuariId: 'usr-001' });
  // ...i prou. No hi ha ni una assercio.
});

Aquesta prova executa tot el fitxer, puja la cobertura vint punts i no pot fallar mai llevat que el codi llanci. Si registrar desés l'esdeveniment amb l'usuari equivocat, a la col·lecció equivocada o amb la data de l'any passat, continuaria verda. Encara pitjor: ara existeix una prova que s'ha de mantenir, que triga, i que dona la falsa impressió que l'auditoria està provada, així que la propera persona no escriurà la prova de veritat perquè «ja n'hi ha una».

Els últims punts són a més els més cars. Anar del 80 % al 90 % sol ser feina útil —branques d'error, casos límit, validacions—; anar del 95 % al 100 % sol significar inventar escenaris artificials per disparar catch defensius que només passarien si Node es trenqués.

Si la cobertura no diu si les teves proves són bones, què ho diu? Les proves de mutació, una idea tan elegant com incòmoda: una eina modifica automàticament el teu codi de producció —canvia un >= per >, un && per ||, esborra una línia, inverteix un booleà— i torna a executar la bateria. Si les proves fallen, han «matat el mutant»: detecten aquella fallada. Si passen amb el codi mutat, el mutant ha sobreviscut i tens codi executat però no verificat. El resultat és una puntuació de mutació, que mesura una cosa molt més propera al que ens importa: no quant codi s'executa, sinó quant està realment vigilat. A Node l'eina de referència és Stryker Mutator, i el seu inconvenient és el cost, perquè executa la bateria una vegada per mutant. La pràctica raonable és aplicar-la només sobre el codi crític —a Escena Viva, src/domini/ i src/autoritzacio/— i de manera periòdica, no a cada commit. L'esmentem perquè és l'antídot conceptual contra la fe cega en el percentatge.

On és el risc a Escena Viva

No tot el codi mereix la mateixa cobertura. Aquest és el nostre repartiment raonat:

Capa Objectiu Per què
src/autoritzacio/ 95-100 % Funcions pures, baratíssimes de provar, i una fallada és una bretxa de seguretat
src/domini/ 90-95 % Regles de negoci; una fallada ven entrades que no existeixen. Sense dependències
tokens.js, contrasenyes.js 85-90 % Seguretat, amb moltes branques d'error que cal forçar amb dobles
src/middleware/ 80 % S'exercita molt per integració; les branques rares mereixen prova unitària
src/controladors/ 70-80 % Coberts sobretot per integració; la seva lògica pròpia hauria de ser poca
src/repositoris/ 70 % Coberts per integració amb base de dades real
src/rutes/ 60 % Gairebé declaratius; el que importa és que responguin, i això ho mesura integració
scripts/, migrations/ Exclosos Eines d'un sol ús

La lectura d'aquesta taula és el missatge central de la lliçó: la cobertura no es persegueix de manera uniforme, es dirigeix cap a on fa mal la fallada. Un 100 % a src/rutes/ no protegeix de res; un 70 % a src/autoritzacio/ és negligència.

Organitzar els scripts d'npm

Amb la bateria creixent, un sol npm test no n'hi ha prou:

{
  "pretest": "npm run lint",
  "test": "mocha",
  "test:unitat": "mocha test/unitat/**/*.test.js",
  "test:integracio": "mocha test/integracio/**/*.test.js",
  "test:veure": "mocha --watch --reporter=min test/unitat/**/*.test.js",
  "test:cobertura": "c8 mocha",
  "test:ci": "c8 --check-coverage mocha --forbid-only",
  "comprovar": "npm run lint && npm run test:cobertura"
}

Les decisions que hi ha darrere. pretest s'executa automàticament abans de test per convenció d'npm, així que si el linter falla les proves ni arrenquen i un error de sintaxi es detecta en un segon en comptes de en trenta. test:unitat i test:integracio van separats perquè tenen naturalesa diferent: les unitàries triguen mil·lisegons i les llances cada dos minuts, les d'integració necessiten base de dades. test:veure només executa les unitàries i amb l'informe min: és l'script que deixes obert en un terminal mentre programes, i posar-hi les d'integració el faria inservible. test:ci és la variant per al servidor, amb llindars i .only prohibit. I comprovar és el «puc pujar això?» previ a un pull request.

Proves ràpides i fiables

Mocha pot repartir els fitxers entre diversos processos amb mocha --parallel --jobs 4. A les unitàries és un guany net, perquè no comparteixen res. A les d'integració contra una base compartida és un desastre, i convé entendre per què: cada treballador executa el seu propi beforeEach amb sembrarDadesDeProva(), que esborra les col·leccions, així que si el treballador A esborra la base mentre B comprova l'aforament de ses-001-1, B falla per un motiu aliè al seu codi i de manera diferent a cada execució. Les sortides possibles són no paral·lelitzar la integració (la nostra elecció: són poques i triguen segons), donar una base per treballador fent servir process.env.MOCHA_WORKER_ID al nom, o aïllar per espai de noms perquè cada prova faci servir identificadors propis i no esborri res global.

Un ajust de velocitat molt rendible i específic d'aquest projecte: bcrypt amb cost 12 triga uns 300 ms a propòsit, i a .env.test hi pots posar BCRYPT_COST=4. Amb dues condicions: que src/config/index.js el llegeixi, i que existeixi una prova que verifiqui que en producció el cost és 12; altrament acabes desplegant hashes febles.

Altres eines: mocha --bail s'atura a la primera fallada, útil quan ja saps que hi ha alguna cosa trencada; --sort dona un ordre alfabètic determinista; i --retries 2 reintenta les fallides, amb un advertiment seriós. Reintentar amaga les proves intermitents en lloc d'arreglar-les, i una prova intermitent gairebé sempre indica una fallada real de concurrència o d'estat al teu codi, no a la prova; fes-ho servir, si de cas, només en extrem a extrem i amb un termini per investigar. El contrari sí que és molt recomanable: aleatoritzar l'ordre per descobrir dependències ocultes. Mocha no ho porta de sèrie, però executar els fitxers en ordre invers de tant en tant (mocha $(ls test/unitat/*.test.js | sort -r)) és informació valuosíssima i gratuïta: si la bateria falla amb un altre ordre, tens estat compartit.

Caçar una prova intermitent

Una prova intermitent (flaky) passa unes vegades i falla unes altres sense que el codi canviï. És el càncer de les bateries: erosiona la confiança fins que l'equip reexecuta CI per costum, i en aquell moment la bateria ja no informa de res.

Causa Símptoma Diagnòstic Solució
Temps real Falla en canviar de dia, mes o any; o en màquines lentes Apareix Date.now() o setTimeout al codi provat Rellotge fals de Sinon (09-03)
Ordre d'execució Passa sola, falla en bateria (o al revés) Executa el fitxer sol i la bateria en ordre invers Estat net a beforeEach, mai a before
Estat compartit Falla la segona execució seguida Variables de mòdul, memòries cau, dades que sobreviuen Funció de reinici, sinon.restore(), netejar la base
Promeses no esperades Fallada en una prova posterior, o unhandledRejection solt Busca it sense async, await oblidats Esperar-ho sempre tot
Ports o recursos fixos EADDRINUSE; falla només a CI Un listen(3000) en algun lloc Port efímer (supertest ja ho fa)

El mètode de caça, pas a pas:

# 1. Reproduir: executar la prova sospitosa cinquanta vegades
for i in $(seq 1 50); do
  npx mocha test/integracio/comandes.test.js --grep "no sobreven" || echo "FALLADA a $i";
done

# 2. Falla sola o nomes en bateria?  3. Depen de l'ordre?
npx mocha test/integracio/comandes.test.js
npx mocha $(ls test/**/*.test.js | sort -r)

I la regla de gestió, tan important com la tècnica: una prova intermitent s'arregla o s'esborra, mai no s'ignora. Si no la pots arreglar ara, marca-la amb .skip i un comentari amb el motiu i la data. Una prova desactivada i documentada és honesta; una que falla a l'atzar és soroll que ensenya l'equip a ignorar el color vermell.

Preparar el projecte per a integració contínua

Al mòdul 11 muntarem el flux complet de GitHub Actions. Aquí deixem el projecte en condicions que qualsevol servidor pugui executar les proves sense intervenció humana. Els requisits són sis.

1. Instal·lació reproduïble. A CI es fa servir npm ci, no npm install: instal·la exactament el que diu package-lock.json, esborrant node_modules abans. Això exigeix que el lock estigui versionat i actualitzat.

2. Configuració per variables d'entorn, no per fitxers locals. .env.test és al .gitignore, així que a CI les variables s'injecten; per això el mòdul 6 va deixar src/config/index.js com a únic punt de lectura de process.env, i canviar d'entorn no toca el codi. Documenta les variables necessàries en un .env.test.example que sí que es versiona, amb NODE_ENV, les URI de les bases amb sufix _test, JWT_SECRET=canviar-a-cada-entorn, BCRYPT_COST=4, NIVELL_REGISTRE=silent i TZ=UTC, sense secrets reals.

3. Base de dades efímera. El servidor aixeca Mongo i PostgreSQL com a serveis de la tasca, buits, i la nostra llavor al beforeEach els deixa a punt. El requisit és que la bateria no assumeixi dades preexistents: si alguna prova depèn d'una dada que vas inserir a mà a la teva màquina, fallarà a CI i no en local, que és la pitjor combinació possible.

4. Sortida amb codi diferent de zero, que és el que un servidor de CI entén com a fallada. mocha surt amb el nombre de proves fallides i c8 --check-coverage surt amb 1 si no es compleix el llindar; comprova-ho amb npm run test:ci; echo "codi de sortida: $?".

5. Sense interacció i sense processos que quedin vius. Res de prompt ni d'esperar una tecla; i amb "exit": false al .mocharc.json, una connexió sense tancar penja la tasca fins que el servidor la mati per temps. Per això l'afterAll amb desconnectar() de la lliçó anterior no és cosmètica.

6. Determinisme horari. Els servidors de CI corren en UTC, així que si alguna prova assumeix la teva zona horària fallarà allà: fixa TZ=UTC a .env.test i treballa amb dates ISO, com venim fent.

Amb això, el flux del mòdul 11 es redueix a npm ci, aixecar els serveis i npm run test:ci. La feina dura és aquesta, no el YAML.

Ganxos de git

Els llindars i els scripts només serveixen si algú els executa. Un ganxo de git executa una ordre abans de completar una operació, de manera que la comprovació deixa de dependre de la disciplina de cadascú; el més útil és pre-commit, que pot impedir un commit si alguna cosa falla.

npm install --save-dev husky lint-staged
npx husky init          # crea .husky/pre-commit

A .husky/pre-commit hi posem npx lint-staged seguit de npm run test:unitat, i al package.json la configuració de què fer amb cada tipus de fitxer:

{
  "lint-staged": {
    "*.js": ["eslint --fix", "prettier --write"],
    "*.{json,md}": ["prettier --write"]
  }
}

lint-staged és la clau de l'equilibri: només processa els fitxers de l'àrea de preparació, no el projecte sencer. Corregir el format de tres fitxers triga mig segon; fer-ho de dos-cents, quinze.

Moment Què s'executa Pressupost de temps
pre-commit lint-staged + proves unitàries Menys de 10 segons
pre-push Bateria completa amb integració Menys de 2 minuts
CI (mòdul 11) Tot, més cobertura i auditoria de dependències El que calgui

I l'advertiment: no converteixis cada commit en una espera de cinc minuts. Si el ganxo és lent, la gent aprèn a escriure git commit --no-verify i el ganxo deixa d'existir. Un ganxo ràpid que s'executa sempre protegeix infinitament més que un d'exhaustiu que tothom esquiva.

Aquesta idea mereix tancar-se amb la conseqüència menys tècnica i més determinant del mòdul: una bateria lenta no falla, s'abandona, i la seqüència és sempre la mateixa. La bateria triga vuit minuts i la gent deixa d'executar-la en local, confiant en CI. Com que el cicle de realimentació passa de segons a minuts, es fan commits més grans i menys freqüents. Quan CI es posa vermell, el canvi ja és enorme i localitzar-ne la causa costa. Apareix una prova intermitent i, com que reexecutar costa vuit minuts, algú hi afegeix --retries. Una fallada real s'amaga darrere d'un reintent i arriba a producció. Conclusió de l'equip: «les proves no serveixen de res». I amb aquella bateria, és veritat. Per això la piràmide té la forma que té, per això separem test:unitat de test:integracio i per això abaixem el cost de bcrypt en proves: una bateria de dos segons s'executa cent vegades al dia; una de dos minuts, dues.

Errors Comuns i Consells

  • Confondre cobertura amb qualitat. Un 95 % amb proves sense assercions protegeix menys que un 70 % amb proves exigents.
  • No activar all: true. Els fitxers sense cap prova desapareixen de l'informe i la mitjana menteix descaradament.
  • Abaixar un llindar perquè passi el lliurament. El trinquet es trenca una sola vegada i ja no torna a pujar.
  • Excloure el que és difícil de provar. És justament el que cal provar; la dificultat sol venir de l'acoblament (09-03).
  • Paral·lelitzar la integració amb base compartida. Fallades aleatòries impossibles de reproduir.
  • Abusar de --retries. Converteix una fallada real de concurrència en soroll tolerat.
  • Ganxos de pre-commit lents. S'acaben esquivant amb --no-verify.
  • Consell: quan la cobertura de branques d'un fitxer sigui molt menor que la de línies, obre'n l'informe HTML; les branques vermelles solen ser exactament els catch i els casos d'error sense provar.
  • Consell: revisa la cobertura del que canvia a cada pull request, no la global. La mitjana del projecte es mou molt a poc a poc i amaga que el mòdul nou hi va entrar amb un 20 %.

Exercicis

Exercici 1: trobar la branca sense provar

Executa npm run test:cobertura i obre l'informe HTML. Localitza la funció de src/serveis/tokens.js amb menys cobertura de branques, identifica quin escenari falta i escriu la prova que el cobreixi. Pista: gairebé sempre és un camí d'error que requereix un doble de Sinon.

Exercici 2: el llindar trinquet

Configura .c8rc.json amb els llindars actuals menys tres punts. Després escriu tres proves noves per a src/domini/gestor-vendes.js que cobreixin les línies 48-51 de l'informe d'exemple, torna a mesurar i puja els llindars al nou nivell menys tres. Documenta la data i el valor perquè el trinquet sigui visible.

Exercici 3: fabricar i caçar una intermitent

Escriu a propòsit una prova intermitent: una que comprovi que el codi d'entrada de la comanda creada comença per EV-2026- fent servir l'any real del sistema. Després explica en quin moment fallarà, demostra-ho amb un rellotge fals situat a 2027-01-01 i arregla-la de manera que no depengui del calendari.

Solucions

Exercici 1. La funció amb pitjor cobertura de branques sol ser rotarRefresc, perquè el camí feliç es prova i els d'error no. L'escenari que falta és la reutilització d'un token ja rotat:

it('revoca la familia sencera si es presenta un refresc ja rotat', async () => {
  const repositori = {
    cercarToken: sinon.stub().resolves({ familia: 'fam-001', usatEl: '2026-10-01T10:00:00.000Z' }),
    revocarFamilia: sinon.stub().resolves(),
  };

  let capturat = null;
  try {
    await rotarRefresc('token-antic', { repositori });
    expect.fail('S esperava ErrorDAutenticacio per reutilitzacio');
  } catch (error) { capturat = error; }

  expect(capturat.codi).to.equal('REFRESC_REUTILITZAT');
  expect(repositori.revocarFamilia.calledOnceWith('fam-001')).to.be.true;
});

L'asserció sobre revocarFamilia és l'essencial: no n'hi ha prou de rebutjar la petició, cal invalidar tota la cadena perquè el token pot haver estat robat.

Exercici 2. El .c8rc.json queda amb "lines": 78, "branches": 68, "functions": 78 si la cobertura actual és 81/72/84. Les tres proves per a les línies 48-51 de gestor-vendes.js —el camí que no emet aforament-baix quan la sessió ja era per sota del llindar, el que ignora vendes de quantitat zero i el de la sessió ja exhaurida— són exactament la mena de branca defensiva que la cobertura treu a la llum. Un cop cobertes, el nou valor podria ser 84/76/85 i els llindars passarien a 81/73/82. I el trinquet es documenta al README, no a la memòria de ningú.

Exercici 3. Fallarà l'1 de gener del 2027 a les 00:00 sense que ningú hagi tocat el codi, i també en un servidor de CI amb la zona horària desplaçada si l'execució cau just al canvi d'any. La demostració consisteix a envoltar la compra amb sinon.useFakeTimers(new Date('2027-01-01T00:00:01.000Z')) i comprovar que l'asserció to.match(/^EV-2026-/) falla perquè arriba EV-2027-. L'arranjament té dues variants vàlides:

// Variant 1: afirmar el FORMAT, no l'any concret
expect(body.entrades[0].codi).to.match(/^EV-\d{4}-\d{6}$/);

// Variant 2: si l'any importa, fixar-lo amb el rellotge fals
const rellotge = sinon.useFakeTimers(new Date('2026-11-05T12:00:00.000Z'));
// ...la compra, i despres
expect(body.entrades[0].codi).to.match(/^EV-2026-/);
rellotge.restore();

La lliçó general: una prova no ha de dependre mai d'una dada que canvia sola. O afirmes la propietat estable (el format), o controles la font de variabilitat (el rellotge).

Conclusió

Ja no només tenim proves: sabem què protegeixen i què no. Entenem les quatre mètriques de cobertura i per què la de branques és la que importa, tenim c8 connectat amb informe de text i HTML, llindars al .c8rc.json que funcionen com a trinquet, exclusions justificades i un repartiment d'objectius per capa que dirigeix l'esforç cap a on la fallada fa mal: la política d'autorització i el domini, no les rutes.

Sabem també que el 100 % és un parany, que una prova sense assercions puja la mètrica sense protegir res, i que la resposta seriosa a «són bones les meves proves?» són les proves de mutació. Tenim els scripts d'npm organitzats amb pretest, test:veure per al cicle curt i test:ci per al servidor. Sabem caçar una prova intermitent amb un mètode, no amb reintents. I el projecte està a punt perquè un servidor d'integració contínua l'executi sol: npm ci, variables d'entorn, base efímera i codi de sortida diferent de zero. El flux de GitHub Actions complet ens espera al mòdul 11.

Queda una última peça. Les proves et diuen que alguna cosa està trencada; gairebé mai per què. Quan una prova d'integració retorna 500 en lloc de 201, quan el procés no acaba i no saps quin recurs ha quedat obert, quan la memòria del servidor creix sense parar durant la setmana, cal una altra eina.

A la lliçó següent, Depuració d'Aplicacions Node.js, tanquem el mòdul: del console.log ben fet servir a l'inspector de V8, punts d'interrupció condicionals, depurar les mateixes proves de Mocha des de VS Code, llegir traces asíncrones, diagnosticar EADDRINUSE, E11000, unhandledRejection i el procés que no mor, trobar una fuita de memòria comparant instantànies del munt, i un mètode de depuració que acaba on ha començat aquest mòdul: escrivint una prova que reprodueixi la fallada abans d'arreglar-la.

Curs de Node.js: De Principiant a Avançat

Mòdul 1: Introducció a Node.js

Mòdul 2: Conceptes Bàsics

Mòdul 3: Sistema de Fitxers i E/S

Mòdul 4: HTTP i Servidors Web

Mòdul 5: NPM i Gestió de Paquets

Mòdul 6: Framework Express.js

Mòdul 7: Bases de Dades i ORMs

Mòdul 8: Autenticació i Autorització

Mòdul 9: Proves i Depuració

Mòdul 10: Temes Avançats

Mòdul 11: Desplegament i DevOps

Mòdul 12: Projectes del Món Real

© Copyright 2026. Tots els drets reservats