Visualizzazione post con etichetta Metrics. Mostra tutti i post
Visualizzazione post con etichetta Metrics. Mostra tutti i post

[10years] Test



I principi base di una buona suite di test sono condivisi tra tutti: 

  • efficaci
  • veloci
  • manutenibili ..
Entrando in maggiore dettaglio iniziano numerose guerre di religione e filosofie differenti e non sempre conciliabili.

Code Coverage


Nel ricordare la peggiore esperienza nella scrittura di test mi viene in mente un cliente che richiedeva a tutti i team di sviluppo di raggiungere una code coverage non inferiore al 90 % . 
In quel momento i vari team avevano una copertura di circa il 20 %, ed hanno interrotto gli sviluppi per aderire alla nuova regola. 
Mi ricordo che, trovando assurda quella scelta, scrissi un test che via reflection andava a verificare tutti i getter / setter in automatico, aiutando i team a raggiungere l'obiettivo. Chiaramente era una provocazione sottile, ma formalmente corretta.

Che conseguenza ha portato l'introduzione di quella regola ?  Le logiche di programmazione si erano spostate dal codice java ai sistemi dove lo strumento di code coverage non aveva 'potere': il database con stored procedures complesse e le view jsp. 

Dire quindi che è importante avere 'tanti' test non ha chissà che valore, rispetto ad averne anche pochi ma assai efficaci.
 
Ma quale principio ci guida nella scrittura dei test? 
Il peggiore che abbia mai incontrati è 

Il principio delle scimmia



piu volte enunciato da vari colleghi incontrati nella storia: se di notte una scimmia cancella una riga a caso della tua code base , almeno un test si deve rompere.

E' un principio sensato, se non fosse che: no, non ci sono scimmie di quel genere! E' anche vero che un qualsiasi programmatore può cancellare una riga per sbaglio...ma non ha senso basare la tua strategia di scrittura di test su una paura irrazionale e statisticamente improbabile. 

Un altro principio che reputo errato è 

Gli acceptance test devono essere end-to-end



Tale principio è arrivato tardi nella mia esperienza lavorativa. I primi AT (Acceptance Test) li scrissi con Fit/Fitnesse. Avevano forma tabellare o erano narrativi, e interrogavano le logiche di business implementate nel dominio. L'input, in qualsiasi forma arrivasse, tramite chiamate http , o file di testo, o comandi in una shell, non era importante. Era importante la logica sottostante, il motore del tuo progetto.

Dopo alcuni anni ho notato questa uguaglianza logica nella testa di molti: un AT deve esercitare i sistemi "veri". Ad esempio se è una webapp devono girare in un browser.

Secondo me i test end to end non sono acceptance test nella loro idea originaria. 
Al di là della nomenclatura, anche i test di 'integrazione' con i sistemi esterni che vedo scritti in giro non vanno bene! 

I test di integrazione devono garantire che il tuo sistema sa interpretare correttamente i dati di input e che costruisce corretti dati di output, in totale aderenza ad un protocollo comunicativo. 
Ad esempio durante lo sviluppo di un ecommerce che si basa su paypal, non ha senso che i tuoi test passino dalla sandbox simulando il giro completo effettuato da un utente. 
In tal modo ti stai legando al funzionamento di un sistema che non è sotto il tuo controllo e i tuoi test stanno testando il comportamento di un altro sistema su cui non stai lavorando !
E' invece importante garantire che le chiamate http scambiate tra il tuo ecommerce e paypal siano corrette ! (ma senza necessità di una simulazione via browser) 

Riassumendo: 
  • gli AT devono esplicitare la logica applicativa. Non è necessario che siano di integrazione
  • i test di integrazione devono testare solo il protocollo di comunicazione (input e output) e basta !

Buoni principi


Per metterti in condizione di poter testare il tuo codice le dipendenze vanno iniettate. Non sono particolarmente a favore di librerie di dependency injection (non ti danno nulla in piu di farlo tu direttamente) ma non è questo il punto. E' importante non avere dipendenze dirette nel codice ! 

Portando questo principio su diversi livelli, dal singolo oggetto al modulo applicativo, riuscirai a trasformare il tuo sistema in una 'stanza dei bottoni' dove, a piacere, puoi switchare tra un database vero e uno in memoria, tra un sistema di messaggistica via code vero a uno finto, tra una dipendenza vera a una finta. 
Questo tipo di flessibilità l'ho visto applicato solo in poche occasioni in team molto maturi. Ma quando ho avuto modo di lavorare su un progetto dominato dalla 'stanza dei bottoni' il divertimento e la produttività sono stati a livelli superlativi ! 



In quasi tutti i progetti che ho lavorato ho sempre spinto verso l'adozione di 'builder' : oggetti che ti costruiscono un oggetto di dominio di 'default' , con tutte le dipendenze necessarie e che va bene nella maggior parte dei casi. 


Ad esempio in un progetto di ecommerce, è utile avere un 'productBuilder' che , a fronte della chiamata di un metodo 'build' ti dà un prodotto generico , disponibile in magazzino, con un prezzo valido, una descrizione normale etc.
La maggior parte dei test lavora su piccole variazioni in merito, ed è pertanto importante costruire una struttura che ti dia facilmente il default ma che ti permetta anche di iniettare piccole modifiche. Il product builder potrebbe essere customizzato cosi: 

new productbuilder.build()  
new productbuilder.withName('something').build()

new productbuilder.withPrice(12).build()
new productbuilder.withQuantity(5).build() ...

Una tipologia di test difficili da scrivere e ideare, ma assai utili, sono quelli relativi ad uno 'scenario' . I builder rappresentano in vitro ciò che si può fare ad un più elevato livello di astrazione: la costruzione di uno scenario 'tipo' e la possibilità di introdurre flessibilità al fine di verificare il comportamento del tuo sistema. 
Voglio rimanere volutamente generico, ma quando sono riuscito a creare dei test di scenario con i miei compagni di team ne ho trovato davvero molto giovamento! 

[Design] Pattern observer


Un pattern assai famoso che abbiamo usato pesantemente nell'ultimo progetto
è l' OBSERVER
Abbiamo osservato tutti gli eventi significativi che succedono; tipicamente: 

  • l'evento scatenante di ingresso che scatena un certo use case
  • tutte le chiamate verso i sistemi esterni e le risposte ottenute
  • la risposta dello use case invocato


Tale struttura è tornata utile per le seguenti implementazioni concrete: 


  • 'database' observer, che a fronte degli eventi interessati esegue delle operazioni sul database, tipicamente l'aggiornamento di timestamp di una macchina a stati
  • 'logger' observer, che stampa su log gli avvenimenti significativi del sistema.
  • 'latency' observer, che calcola il tempo di esecuzione di uno use case
  • 'statistic' observer, che memorizza in stutture dati l'andamento delle chiamatesecondo particolari regole di business (ad esempio quali e quanti use case sono statiscatenati in un intervallo di tempo) 
  • 'audit' observer, per salvare su database quale utente sta eseguendo una operazione sensibile


Tutti questi observer sono richiamati da un observer list iniettato nell'oggetto che incapsula il flusso principale.

Ad esempio se vogliamo notificare l'evento di 'messaggio in ingresso' : 

 public void execute(IncomingMessage message) {
    observerList.notifyIncomingMessage(message);
    doSomething()...
 }

 dove observerList è una implementazione concreta dell'interfaccia CustomObserver : 

 public class ObserverList implements CustomObserver {
    private final List observers;
    public void notifyIncomingMessage(IncomingMessage message) {
  for (CustomObserver customObserver : observers) {
customObserver.notifyIncomingMessage(message);
}
  }

Grazie a questo pattern il codice principale è ignaro dei dettagli a contorno. 

Mi piace anche l'effetto teorico che puoi sfilare un certo observer in modo che il sistema
continui ad andare
Immagino una situazione estrema dove ad esempio si scopre che il 'logger' observer  per un qualche baco sta rallentando pesantemente la produzione. Nel frattempo che si indaga il baco si potrebbe rimuovere questo observer dalla ObserverList e tutto continuerebbe ad andare, senza causare disservizio.

[Performance] memory leak analysis with JProfiler for a java webapp


I wrote a first draft that explains how to use JProfiler to analyze possible memory leaks in a classical Java application.
Following the article: https://github.com/tortitommaso/PerformanceAnalysis/raw/master/MemoryLeak.pdf
You can follow this tutorial to gain a method to conduct such an analysis. Good luck.

[Metriche] Le uniche metriche del codice che servono sono qui


Molto si è detto e scritto circa quali metriche è opportuno considerare durante lo sviluppo di un progetto.

Poichè le metriche servono esclusivamente a incoraggiare un comportamento, trascrivo qui quelle che piacciono a me, seguendo le quali si riuscirà a diventare un ottimo programmatore.
Seguitemi:

> Minimizzare il numero di user stories lavorate in modalità Last In First Out
Quando l'ultima user story descritta è sempre la più importante vuol dire che non c'è un vero e proprio planning e si è perso qualsiasi concetto di pianificazione e priorità: il vostro progetto sta andando a ramengo.


> Profondità coverage
Sapere che l' 80 % del codice è testato è una buona cosa. Eppure sapere che dalla stessa riga di codice ci passano più test in generale può voler dire che sono stati scritti non solo dei test unitari, ma anche dei cluster test o degli integration (o acceptance) test.
Se due test di diversa natura passano dalla stessa riga di codice la profondità di coverage è 2, che è meglio di una profondità di 1.


> Massimizzare la varietà di dati di test (suggerita dal mio collega Marco Gulino)
Non siamo abituati a pensare alle boundary conditions. Un eventuale codice del tipo: if (i < i ="=" i =" MAX_INT" i =" -1" style="font-weight: bold;">> Monitorare il rapporto Errore di stima / Numero di framework usati

L'errore di stima cresce a seconda del numero di tecnologie attraversate. Per completare la vostra user story dovete toccare solo maven, java, spring ? Oppure anche hibernate, fitnesse, wicket, selenium etc..? Calcolate un fattore correttivo adeguato!


> Duplicazione divergente
Certo la duplicazione è un male: ogni modifica in un codice copiato va riportato paro paro da altre parti e tale processo è error-prone, ma alcuni eroi con un po' di lavorio con search&replace e espressioni regolari riescono senza dubbio a lavorare in questa modalità. Eppure se i pezzi di codice duplicato divergono si perde la capacità eccezionalmente umana di riconoscere ad occhio le differenze. Il tuo codice non solo è duplicato ma non è più nemmeno simmetrico!


> Il rapporto Numero di classi / Numero di metodi pubblici deve tendere a uno
Una classe, una responsabilità, un solo metodo pubblico. E' facile identificare di quante responsabilità è caricato un oggetto: il piu delle volte basta contare il numero di metodi privati


> Il tempo di esecuzione test non deve degenerare all'aumentare del numero di test
Per lo meno garantisci che rimanga costante al crescere del numero di test. 100 test ci mettono 100 secondi? Se 200 test ci mettono 200 secondi stai adottando una strategia non peggiorativa.


> Minimizzare il numero di user stories che tornano "in progress"
Non mi ricordo chi l'ha detta, ma è di certo sintomo che gli AT non sono efficaci, e rilavorare funzionalità considerate finite è frustrante e puà essere dannoso se chi sistema i bachi non coincide con chi ci ha lavorato.