Young programmer or IT specialist satisfied with her work done_Code Coverage: Asegura la Calidad en el Testeo de Desarrollos

Code Coverage: Garantia de Qualitat en les Proves

El desenvolupament d’aplicacions requereix una prova contínua i completa. Quan no provem funcionalitats i casos d’ús, obrim la porta a la aparició d’errors. Amb la Integració Contínua (CI), implementem l’automatització de proves que s’executen com a part de la canalització (pipeline).

A més de superar les proves automàtiques, hem de vetllar perquè siguin completes per cobrir totes les possibilitats.

Per aconseguir aquesta garantia, fem servir el Code Coverage.

¿Qué és Code Coverage?

Code Coverage és una métrica que podem analitzar després de realitzar proves automàtiques. Aquesta métrica indica quina part del total del codi ha estat realment testejada per les proves. És a dir, donades les proves automàtiques, obtenim mètriques de quina part del codi ha estat executada realment amb elles.

Hi ha diferents tipus de Code Coverage, en funció del que analitzem. Per poder entendre-ho millor, utilitzarem un exemple de codi font.

/* coffee.js */ 
 
export function calcCoffeeIngredient(coffeeName, cup = 1) { 
  let espresso, water; 
 
  if (coffeeName === 'espresso') { 
    espresso = 30 * cup; 
    return { espresso }; 
  } 
 
  if (coffeeName === 'americano') { 
    espresso = 30 * cup; water = 70 * cup; 
    return { espresso, water }; 
  } 
 
  return {}; 
} 
 
export function isValidCoffee(name) { 
  return ['espresso', 'americano', 'mocha'].includes(name); 
} 

 

En aquest codi, definim un parell de funcions senzilles. Una que retorna ingredients segons el tipus de cafè i una altra que indica quins tipus de cafè acceptem com a vàlids.

Sobre aquest codi, realitzem aquestes proves automàtiques:

/* coffee.test.js */ 
 
import { describe, expect, assert, it } from 'vitest'; 
import { calcCoffeeIngredient } from '../src/coffee-incomplete'; 
 
describe('Coffee', () => { 
  it('should have espresso', () => { 
    const result = calcCoffeeIngredient('espresso', 2); 
    expect(result).to.deep.equal({ espresso: 60 }); 
  }); 
 
  it('should have nothing', () => { 
    const result = calcCoffeeIngredient('unknown'); 
    expect(result).to.deep.equal({}); 
  }); 
}); 

En la prova, sol·licitem ingredients per a 2 espressos (hauria de donar-nos 60 en total, ja que cadascun porta 30). Després, sol·licitem ingredients per a un cafè ‘unknown’, que hauria de donar-nos un resultat buit.

Aquesta prova està testejant el codi anterior, però quin Code Coverage està fent del mateix?

Line Coverage

Per començar, tenim la mètrica de Line Coverage. Aquesta mètrica simplement calcula el total de línies executable del codi original i el total de línies que hem executat realment amb la prova. En el codi original, les línies executable són les ressaltades:

/* coffee.js */ 
 
export function calcCoffeeIngredient(coffeeName, cup = 1) { 
  let espresso, water; 
 
 #14C8BE
  } 
 
  if (coffeeName === 'americano') { 
    espresso = 30 * cup; water = 70 * cup; 
    return { espresso, water }; 
  } 
 
  return {}; 
} 
 
export function isValidCoffee(name) { 
  return ['espresso', 'americano', 'mocha'].includes(name); 
} 

 

En realitzar aquesta prova, a la primera part mirem si el nom és ‘espresso’, entrem a la primera condició i acabem la prova. A la segona part mirem si el nom és ‘espresso’, no ho és i mirem si és ‘americano’, no ho és i retornem un conjunt buit.

Per tant, a la prova no estem accedint al codi de la segona condició (al que entrem si és ‘americano’). I tampoc provem si el nom del tipus de cafè és vàlid, que es comprova en una altra funció. En total hem passat per 5 de les 8 línies executable, el que ens dóna un Line Coverage del 62.5%.

Line Coverage no considera línies que siguin de definició i no s’executin. Line Coverage dóna una primera aproximació bona que el codi s’està executant, però no és suficient. Com veiem, hi ha una funció sencera que no ha estat testejada. I amb només Line Coverage podríem pensar que sí que la hem testejat, però al 62.5%. Per tenir una visió més completa, hem de fer servir altres tipus de Coverage per veure, per exemple, quantes funcions han estat testejades.

Statement Coverage 

Aquest tipus de Coverage ens indica els statements executats dins del codi. D’entrada, pot semblar el mateix que el Line Coverage, i moltes vegades ho és, però es pot donar el cas que una línia tingui més d’un statement. Per exemple, en el nostre codi, aquesta línia:

    espresso = 30 * cup; water = 70 * cup; 

 
Aquí en una sola línia tenim dos statements. El que ens deixa el total de statements a 9, i la nostra prova només executa 5, deixant un Coverage del 55.55%.

Function Coverage 

Function Coverage ens indica el total de funcions declarades en el codi que són realment cridades per les proves.

En el nostre cas, tenim dues:

export function calcCoffeeIngredient(coffeeName, cup = 1)  
 

export function isValidCoffee(name) 

Donades aquestes dues funcions, la prova realment només crida a una d’elles. La Function Coverage és del 50%.

Si la prova cridés a totes dues funcions tindríem un 100% de Function Coverage. Però internament podria ser que trossos sencers de codi no s’estiguessin consultant, ja que fem servir condicionals i bucles.

Branch Coverage

El Branch Coverage s’encarrega de comprovar, dins del codi, quantes seccions separades per condicions o bucles realment s’han testejat. En el codi anterior, tenim les següents branques:

export function calcCoffeeIngredient(coffeeName, cup = 1) { 
  // ... 
 
  if (coffeeName === 'espresso') { 
    // ... 
    return { espresso }; 
  } 
 
  if (coffeeName === 'americano') { 
    // ... 
    return { espresso, water }; 
  } 
 
  return {}; 
} 

Donades aquestes branques, tenim les següents opcions:

  1. Cridar a la funció amb només el nom del cafè
  2. Cridar a la funció amb el nom del cafè i el nombre de tasses
  3. Que el cafè sigui ‘espresso’
  4. Que el cafè sigui ‘americano’
  5. Que el cafè sigui d’un altre tipus

La prova que hem realitzat passa per les branques 1, 2, 3 i 5, quedant la 4 sense provar. Per tant, el Branch Coverage és del 80%.

Hi ha altres tipus de Coverage, per exemple alguns que també intenten avaluar la complexitat d’un codi i ponderar el % de complexitat executat, per donar més pes a codi més complex. Però aquests 4 que hem comentat són bàsics i comuns en moltes eines de Code Coverage.

En funció del llenguatge de programació i entorn caldrà fer servir diferents eines per treure les mètriques i obtenir valors, no hi ha una solució única al respecte.

Code Coverage i Integració Contínua

Com hem pogut veure, només un tipus de Coverage donaria una idea parcial. Si només comptem línies o statements, pot ser que les proves no estiguin accedint a un tros de codi o funció vital, però de menor nombre de línies, donant l’aparença que hem provat molt codi, però faltant funcionalitats bàsiques. Mirant només branques ens podem deixar funcions, i mirant només funcions ens podem deixar branques internes importants.

Durant la CI haurem de fer servir eines que ens permetin calcular el Code Coverage que estem realitzant en una aplicació i ens indiquin diverses mètriques de Coverage. Dades aquests resultats, hem d’afegir a la pipeline condicions que aturin l’execució de la mateixa sense un mínim de Coverage en les mètriques obtingudes.

Quan s’inclouguin suficients proves perquè el Coverage augmenti i la pipeline no falli, podrem seguir amb l’execució normal i instal·lació en els diferents entorns. D’aquesta manera, garantim que el codi ha estat provat correctament i una certa qualitat del mateix, de manera automàtica.

Tot i que a tots ens agradaria un món ideal en què hi hagi un Coverage del 100% en tots els tipus, hem d’adaptar les condicions a la realitat. En general, arribar a un Coverage del 80% en desenvolupaments nous ja es considera una molt bona mètrica. A més, es poden donar casos en què es estiguin realitzant proves per a un projecte ja acabat (per exemple, en casos d’aplicacions legacy) i haguem de partir de mètriques mínimes més baixes, com un 30%, i anar pujant a mesura que es desenvolupin aquestes proves.

En general, haurem d’adaptar les condicions a la realitat i situació de cada cas.

Optimitzant la Qualitat del Codi amb una Sòlida Estratègia de Code Coverage

Code Coverage és una mètrica molt potent a l’hora de garantir la qualitat dels projectes i automatitzar el testeig. Ens permet aturar codi no testejat abans que arribi a entorns productius i forçar a pensar en una programació de proves en què realment consultem tot el que pot fer l’aplicació, sense deixar-nos apartats ni funcionalitats.

Amb Code Coverage podem millorar la qualitat de les entregues i les proves i assegurar-nos que noves funcionalitats i desenvolupaments no es facin sense un testejatge automàtic exhaustiu que ens doni seguretat i confiança.

Si tens preguntes, necessites assessorament o desitges discutir com podem col·laborar, no dubtis en posar-te en contacte amb nosaltres.

Daniel Morales Fitó – Cloud Engineer at Itequia