Continuous Integration con Code Coverage e Code Quality, gratis. Troppo bello per essere vero.

travis_codecov_codacySpesso capita di vedere, ancora oggi, lo sviluppo del software affidato a sistemi di versioning (per così dire) del tutto improvvisati come una cartella di rete dove sono depositati una serie di eseguibili con nomi nel rango di:

  • application.jar, 2014/05/01 12:00
  • application_FIX.jar, 2014/05/02 12:30
  • application_FIX_NON_TOCCARE.jar, 2014/05/02 12:31
  • application_FIX_PRODUZIONE_PORCA_PUTT.jar, 2014/05/03 03:31

Da questo si evince anche che la prassi di testing, in questo caso, è:

Fai il deploy in produzione e vedi che succede

Analizziamo un attimo ciò che sembra essere successo in questo caso.

application.jar è stata ultimata e nemmeno mezz’ora dopo abbiamo già un nuovo deploy per un qualche bug, nemmeno un minuto dopo ecco una nuova versione FIX_NON_TOCCARE, evidentemente il fix ha introdotto una regressione, prontamente sistemata. Successivamente alle tre e passa del mattino capita il classico “fix in produzione”. Alzi la mano chi non si è mai trovato in scenari simili. A me personalmente è capitato diverse volte in passato.

Cosa è successo qui? Andiamo per ordine:

  • il primo fix è sintomo di un qualche corner case non individuato in precedenza o anche semplicemente una svista.
  • il fix FIX_NON_TOCCARE è sintomo di mancanza di test (JUnit ad esempio) perchè il fix precedente ha semplicemente rotto i contratti o la business logic.
  • per ultimo, il FIX_PRODUZIONE_PORCA_PUTT capita spesso a causa di test d’integrazione deboli, se non del tutto assenti, con i componenti che poi interagiscono in produzione.

Come possiamo ovviare, o almeno mitigare, tutti i rischi di questo ciclo?

  • Versioning prima di tutto usando SVN, Git, CVS o chi per esso.
  • test unitari e comportamentali durante lo sviluppo, in ambito Java possono essere JUnit e JBehave ad esempio.
  • Continuous Integration, con questi sistemi (ad. es. Jenkins) siamo sicuri che il codice introdotto ad ogni merge non “rompa” la build.
  • Test Coverage per assicurarci che i test coprano correttamente il codice.
  • Code Quality, spesso una semplice analisi statica del codebase è sufficiente per inviduare bug, bad practices o peggio, come ad esempio risorse non rilasciate o flussi di controllo che portano a stati instabili.

Versioning

Non mi addentrerò in modo particolare su questo tema, in rete è disponibile una vasta pletora di articoli, tra i miei preferiti riporto questo. In breve si tratta di usare le rame di feature per le nuove funzionalità. Quando queste vengono ultimate si esegue un merge verso “develop” (preferibilmente con pull request e code review) per poi finalmente far confluire tutti i cambi di develop a “master” e poter così procedere ad una release di una nuova versione.

Testing

Inizialmente scrivere i test unitari può sembrare una sciocchezza. Perchè scrivere un test di un POJO che ad esempio non può accettare un parametro con valore null? È ovvio che non arriverà mai null e nel resto dei componenti farò in modo che non accada mai.

Tutto vero, fino a quando non succede. A volte può succedere perchè non si sono individuati tutti i corner case in fase di stesura del test, altre volte può succedere perchè cambiano le condizioni al contorno quali, ad esempio, il cambio dell’ora legale in un server remoto.

Un altro grosso vantaggio dei test è quando si riprende in mano una codebase per estenderla a distanza di settimane se non mesi. Dopo tutto quel tempo è difficile introdurre cambi e spesso si finisce per rompere funzionalità già esistenti, introducendo quello che in gergo di ingegneria del software si chiama “regressione”.

Un valido punto di partenza per TDD (Test Driven Development) e BDD (Behavioural Driven Development) è lo stub di wikipedia.

Per quanto riguarda il test coverage, in ambito Java, abbiamo vari plugin maven, uno su tutti è Jacoco. Spesso anche gli IDE consentono di valutare la copertura del codice.

Continuous Integration

Il modello di sviluppo con Git, dove ciascun sviluppatore lavora nella propria rama, fa in modo che si possa parallelizare il più possibile lo sviluppo. Successivamente i cambi verranno integrati (attraverso il merge) in una singola branch (develop). Qui viene in aiuto il Continuous Integration (CI).

Il grosso vantaggio del CI è quello che ci evita portarci in locale i cambi delle altre branch in locale per eseguire una build (ad. es. con maven), se invece portiamo tutti i cambi in una singola branch il CI si occuperà di eseguire tutti i test (ed i cambi della codebase) che le varie branch si portano dietro. Nel caso i test siano tutti ok (green in gergo) si può anche procedere successivamente a migrare la rama master con develop e rilasciare una nuova versione (release) con tutti i cambi.

Normalmente per avere un CI è necessario avere in loco una istanza di Jenkins o simili. Oggi però abbiamo anche servizi sulla nuvola e persino free per codice open source quali ad esempio Travis CI. Per una carrellata di altri servizi si può partire da GitHub.

L’utilizzo di Travis è veramente semplice. Si tratta di loggarsi con la propria utenza GitHub ed aggiungere i repo che si vogliono includere nel ciclo di continuous integration. Per questo è sufficiente copiare un file di nome .travis.yml:

sudo: false
language: java
jdk:
  - oraclejdk8

A questo punto basta un commit e push del proprio branch per vedere i risultati nelle console di travis. L’esempio sopra informa travis semplicemente che il linguaggio è java e che va usato l’Oracle JDK.

Tests run: 25, Failures: 0, Errors: 0, Skipped: 0

[INFO] 
[INFO] --- jacoco-maven-plugin:0.7.5.201505241946:report (report) @ life-game ---
[INFO] Analyzed bundle 'life-game' with 18 classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 10.086 s
[INFO] Finished at: 2015-12-27T19:38:51+00:00
[INFO] Final Memory: 19M/344M
[INFO] ------------------------------------------------------------------------

Travis offre molte opzioni di configurazione per l’esecuzione dei test, possiamo avere ad esempio un’istanza mysql, un virtual framebuffer per le applicazioni con GUI e persino firefox se si vogliono svolgere frontend test con selenium. La documentazione è ricchissima e vi invito a leggerla. RTFM, sempre. 🙂

Code Coverage

Come detto prima ci sono molti tool che consentono di valutare la copertura dei test, sia in fase di build che dentro il proprio IDE. Alcuni tool però ci consentono di andare un po’ oltre e producono analisi e suggerimenti su dove intervenire.

Uno di questi strumenti, del tutto gratuito per software open source, è Codecov. Come per Travis CI l’installazione è triviale. Si tratta di collegarsi con il proprio profilo di GitHub e associare i vari repo e successivamente potremo avere i report sul code coverage dopo ogni commit o durante una pull request verso master o develop. Qui vediamo alcune schermate:

codecovcodecov_suggPer far si che il codice sia analizzato basta aggiungere qualche riga al nostro .travis.yml:

sudo: false
language: java

jdk:
  - oraclejdk8

before_script:
  - pip install --user codecov

after_success:
  - codecov

Una feature molto pratica è che codecov-io inserisce un commento in fase di pull request indicando i cambi nel code coverage per la PR. Qui vediamo un merge particolarmente riuscito 🙂

codecov_commentQuesti due tool, di per se, sono già un validissimo complemento per evitare lo scenario iniziale ma ci resta ancora un’arma.

Code Quality e Code Review

Tutti commettiamo errori mentre programmiamo, nessuno escluso. In questo molti compilatori ci aiutano con analisi statiche del codice direttamente dall’IDE o con strumenti esterni come FindBugs o il sempre verde SonarQube.

Ma oltre a commettere errori a volte siamo anche distratti e ci dimentichiamo di eseguire un check. Altre volte il tempo non ci permette di analizzare il codice e altre volte ancora gli errori arrivano a develop perché non si fanno le code review per mille ragioni, spesso per motivi di tempo, è sempre difficile concordare con il management uno slot di tempo dove chiudere quattro developer in una stanza per una code review. I developer vedono un’ora, un manager vede 4 ore/uomo “sprecate”.

In ogni caso, per chi non vuole limitarsi alla semplice analisi statica del codice dall’IDE (che comunque spesso trascura molti errori ovvi) ci sono strumenti che ci consentono di scrutinare il nostro codebase in maniera automatica ed asincrona. Per il momento mi sto affidando a Codacy.

Ancora una volta la configurazione è abbastanza triviale, si tratta di loggarsi con il proprio profilo di GitHub e iniziare ad associare i repo che si vogliono analizzare.codacyPer ogni repo possiamo configurare parametri quali tipi di issue da ignorare, altre da abilitare, cartelle da trascurare (ad esempio non vogliamo “inquinare” i nostri risultati con i problemi di altre librerie quali bootstrap, jquery, etc).

Anche qui possiamo vedere tutta una serie di statistiche per ogni commit, quali file sorgente presentano problemi e persino ottenere un “voto” (come a scuola) 🙂 per ciascun repo fino all’atomicità di un voto per commit.

codacy_commitConclusioni

Abbiamo visto una veloce carrellata di strumenti che consentono di migliorare notevolmente il processo e la qualità del codice, tutto con strumenti gratuiti e online, senza quindi dover installare server o software (oltre a git!) sulla propria macchina.

Naturalmente gli strumenti di cui sopra si possono usare con molti altri linguaggi e per alcuni di essi ci sono alternative più valide, come scritto prima un ottimo punto di partenza è la pagina di integrazioni su GitHub.

Buon coding!

Leave a Reply

Your email address will not be published. Required fields are marked *