Programming Languages Hacks

Importanti regole per linguaggi di programmazione rilevanti come Java, C, C++, C#…

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Archive for the 'test' Category

Come adottare i test nel mondo reale

Posted by Ricibald on 25th September 2013

Perché testare (è una domanda seria…)

Testare può significare semplicemente elencare una serie di test in un Word, eseguirli secondo input specifici e riportarne l’esito in un file Excel. Significa passare nottate davanti a un Word per essere sicuri di aver scritto tutto, eseguire i test nei vari ambienti remoti con attese interminabili, riportare meticolosamente gli esiti in un foglio excel. Questo in genere ha serve solo a produrre un grafico di avanzamento al cliente, che può avere una panoramica immediata dello stato dei test.

Il motivo per cui uno sviluppatore odia i test è il fatto che non mirano a verificare realmente le funzionalità (per questo bastano anche sessioni di debug) bensì producano elaborati che servono solo per la forma, senza puntare alla sostanza. Eseguire test predeterminati non è efficace nel trovare nuovi bug, bensì fa emergere solo che i test non sono mai stati fatti, probabilmente perché abbiamo perso tempo a produrre questi file Word ed Excel che producono “fuffa” inutile. Condiamo il tutto con la ripetitività delle operazioni, la lentezza degli ambienti, la fretta e le aspettative stressanti e avremmo ciò che nessuno vorrebbe mai fare. Fare cose noiose significa farle male, quindi probabilmente sbagliarle e sentirsi dequalificati. Devo continuare con i contro…?

In realtà scrivere test dovrebbe essere un vero e proprio stile lavorativo, che deve coinvolgere la stesura dei requisiti (i test sono istanze di requisiti) e lo sviluppo in tutto il ciclo di vita. Non si tratta di forma ma di molta, molta sostanza. Scrivere i test in modo che siano semplici da scrivere, veloci da eseguire, isolati e facilmente riadattabili è una vera e propria sfida che richiede molta competenza e che di conseguenza risulta più che appassionante.

Senza noia (scrivere test è stimolante), senza stress (i test migliorano la confidenza nel codice), senza interminabili sessioni remote di troubleshooting (i test evitano regressioni) il lavoro dello sviluppatore si trasforma nell’implementare le nuove funzionalità, che costituisce la parte stimolante (e divertente) del lavoro. Devo continuare con i pro…?

Come testare…

Rispetto a un requisito dovrebbero essere previste le seguenti tipologie di test:

  1. per impedire regressioni nei layer applicativi scrivere i vari unit test secondo approccio TDD (mantra red/green/refactor). Simulare le dipendenze (es. db). Scrivere unit test significa dettagliare tramite esempi concreti il requisito spendendo circa il 50% del tempo dello sviluppo:
    • in mancanza di un input o output ben definito utilizzare il baseline test: testa, salva output in un file e al prossimo test confronta l’output con quanto salvato precedentemente nel file
    • non scrivere test specifici per un certo valore cablato, bensì rendili concettualmente veri non legandoti a un input particolare
    • non accorpare in un singolo test più clausole ma crea più test separati
    • non cambiare o rimuovere un test unitario a meno che non cambi il corrispondente requisito
    • scegli i valori di input dei test seguendo il model-driven testing: testa i valori limite e immediatamente contigui
    • al termine del giro mantra red/green/refactor verifica il code coverage che sia almeno 80% per controllare quali execution path sono stati omessi dai test
    • testare i layer applicativi come se fossero API generiche, senza legarle a un client particolare
  2. per impedire regressioni nel presentation layer (UI) scrivere coded UI test che replicano la pressione dei pulsanti o l’edit delle textbox (usando TFS o Selenium o WatiN). Simulare le dipendenze (es. layer application).
    • attenzione: questi test servono solo a testare la UI in isolamento ma non sostituiscono i test unitari dei layer applicativi
    • per semplificare la manutenzione dei test mantenere, a fronte di modifiche grafiche, gli stessi ID degli elementi grafici per non introdurre una breaking change nel test
  3. per verificare che il requisito sia interamente soddisfatto trascrivere un’istanza del requisito in un test case con i relativi step manuali dettagliati. Può essere trascritto prima o dopo aver prodotto il codice. Deve essere eseguito a implementazione completata in un ambiente completamente integrato. Un sottoinsieme sarà successivamente candidato a essere automatizzato
  4. per verificare che il requisito soddisfi caratteristiche minime creare una serie di smoke test rapidi da eseguire manualmente in sequenza
  5. per ricercare nuovi bug nel requisito condurre exploratory test. Una volta rilevata una situazione anomala è possibile trascrivere in uno script gli step precisi per consentire a chiunque di replicare il bug. Alcuni script significativi saranno successivamente convertiti in test automatizzati per garantire sempre la non regressione
    • gli exploratory test possono essere condotti seguendo diversi tour (equivalenti dei design pattern). Il tour può essere positivo (si verificano storyboard, CRUD di entità e relazioni, cambiamento di comportamento in diversi stati) oppure negativo (si verifiano variazioni insolite, invarianti dentro l’operation envelope)
  6. per verificare che un bug segnalato non si ripresenti più creare sempre un regression test che verifichi il bug. In questo modo lo stesso bug non dovrebbe più regredire
  7. per impedire bug in fase di deploy condurre dei deploy test andando sempre a generare un installer. Può essere un Installer o un IIS Deployment Package o un deployment script o un ClickOnce Deployment, non importa. Il punto è che deve esistere in quanto solo questo lo rende parte integrante del test. L’installer deve poter essere testato, come qualunque altra cosa. Molti bug rilevati sono spesso in realtà problemi in fase di deploy e determinarli con anticipo semplifica molto la gestione

Come procedere…

La sequenza descritta è quello ideale: test automatici unitari all’inizio, poi test integrati manuali e infine test automatici integrati.

Per arrivare a questo risultato un progetto ha però bisogno di tempo e lavoro per mettere in piedi una tale organizzazione. L’errore più comune in questo caso è avere una forma mentale del tipo “o tutto o niente”.

In realtà l’adozione dovrebbe essere progressiva per sostenere il cambiamento.
La progressione tipo dovrebbe essere la seguente:

  1. sfruttare l’IDE delle proprie macchine implementando i test unitari (applicativi e UI)
  2. adottare un sistema di controllo sorgente (TFS, Git, Mercurial, …)
  3. adottare un file system centralizzato per condividere documenti (TFS Portal, Dropbox, cartelle condivise, …)
  4. adottare un sistema condiviso di task & bug management (TFS, Basecamp, Zoho, Teamlab, Planbox, VersionOne, Bugzilla, Bug Genie, …)
  5. adottare un sistema di build management per abilitare il continuous integration (TFS, Jenkins, TeamCity, CruiseControl, DamageControl, …). Impostare una continuous build (“rolling”) che esegue la maggioranza dei test unitari. Impostare una build notturna che esegue test più estesi, come test di performance, di carico e di sistema
  6. adottare un sistema di gestione ed esecuzione dei test (manuali e automatici) per strutturare e automatizzare i test (Microsoft Test Manager, Telerik Test Studio, Tosca Testsuite, TestDrive, Word o Excel condiviso, …)
  7. adottare un sistema per creare automaticamente ambienti virtuali per semplificare i test in ambienti distribuiti (TFS Lab Environments, TestComplete, VmWare, VirtualBox, …)
  8. adottare un sistema per il test e deploy automatico in ambienti distribuiti (virtuali o fisici)

Già arrivare al punto 6 sarebbe un ottimo risultato :-)
Arrivare al punto 8 significa riuscire a rilasciare in produzione senza intervento umano (!).

Per sapere come si classifica il vostro modo di lavorare potete divertirvi a fare il Joel Test. Se realizzate un punteggio di 12/12 vi mando il cv :-)

Posted in pattern, test | No Comments »

Evoluzione dei test – Debugger, TDD, BDD, LINQPad

Posted by Ricibald on 24th June 2013

Uno strumento fondamentale per sviluppare in .NET si sta sempre più rilevando LINQPad.

Nonostante sia nato per analizzare le query prodotte da LINQ, di fatto lo strumento è molto di più: consente infatti di testare al volo porzioni di codice .NET.

Facciamo subito un esempio pratico. Immaginiamo di avere un codice 3-layer con diversi componenti remoti che interagiscono tra di loro mediante protocolli più o meno complessi. Uno di questi componenti deve validare l’indirizzo mail che arriva in input da uno degli altri componenti. Il codice che validerà la mail è il seguente:

Regex.Match(emailAddress, @"^([\w\.\-]+)@([\w\-]+)((\.(\w){2,3})+)$");

Immaginiamo di voler testare questo codice. Come faresti?

Come verifichiamo il nostro codice? Vecchio stile…

In uno sviluppo classico (errato) si procede in questo modo:

  1. si scrive il codice fino a ottenere l’elaborato finale
  2. si testa il codice facendo girare tutti i vari componenti e incrociando le dita… se va tutto bene, bene… sennò si collega il debugger di VS e si passa per giorni e giorni di debugging…

Questo approccio ha tanti limiti tra cui: dipendenza da componenti esterni, tempi tecnici lunghi, non ripetibilità automatica del test.

In uno sviluppo più evoluto (TDD) si farebbe uso di unit test come segue:

  1. si scrive prima il metodo di test che verifica che a fronte di mail corrette non abbiamo errori e che viceversa a fronte di mail errate avremo eccezioni
    • il test dovrà essere isolato e atomico iniettando mock o, meglio, stub
  2. si scrive il minimo codice possibile per far compilare il codice
  3. si esegue il test, che deve fallire (il comportamento non è ancora implementato)
  4. si scrive l’implementazione vera e propria del test
  5. si riesegue il test, che stavolta dovrà andare bene

Questo approccio corregge i limiti sopra esposti consentendo una ripetibilità dei test e un isolamento dei componenti.

Gli unit test sono la risposta allora… Quale è il problema?

Gli unit test sono il modo di procedere corretto ma richiede una competenza di alto livello:

  • bisogna conoscere framework di dependency injection, unit test, mocking
  • bisogna conoscere bene i confini di un test: non bisogna testare né troppo né poco
  • gli input dei test devono essere significativi per verificare i “casi limite”
  • richiedono molto rigore: a ogni bug rilevato bisognerebbe creare il test corrispondente e fixarlo
  • bisogna saper isolare molto bene i test e rifattorizzarli nei punti variabili per evitare che una modifica nel codice comporti una modifica a cascata di tutti i test collegati

Oltre alle competenze richieste, i test di fatto aumentano comunque il lavoro complessivo:

  • bisogna scrivere test e mock per tutti gli input significativi
  • bisogna rifattorizzare i test e mantenerli nel tempo a fronte di cambiamenti anche strutturali

Non sto dicendo non bisogna fare unit test, anzi. Il problema è che una competenza simile è difficile trovarla ed è complicato trovare un progetto con un contesto culturale favorevole a un approccio di questo tipo.

Gli unit test sono troppo complessi…? Allora testiamo solo gli use case…

Allora era giusto il precedente approccio? Torniamo al debbugger con VS? Non credo sia corretto tornare nella propria zona di comfort, l’ideale sarebbe crearsi la competenza per perseguire gli unit test “puri”. Ma una via di mezzo potrebbe comunque esistere.

Si può semplificare e non scrivere 100 test in isolamento arrivando a una copertura del 100% del codice testato, bensì scrivere solo test “integrati” che vadano a verificare end-to-end il comportamento del codice integrato. Questo assomiglia più al BDD (Behavior-driven development) e presenta i seguenti vantaggi:

  • si verificano le porzioni di codice che effettivamente verranno eseguite nei vari casi d’uso
  • non richiede un’infrastruttura di stub/mock: si possono anche non conoscere framework di dependency injection e mocking

Il problema è che comunque i test devono essere atomici: l’esito non deve essere influenzato da altri test. Significa che siamo costretti a creare nel db e/o componenti dipendenti tutte le precondizioni che servono per avere una situazione “pulita” da testare.

Il BDD è ottimo per avere test che verifichino il giro completo del codice in modo ripetibile e quindi automatizzabile. Ma il BDD non è efficace per il nostro semplice caso di test di esempio. Se vogliamo solo testare se l’espressione regolare è valida per un’indirizzo email dovremmo porci in troppe precondizioni per poi fare semplici asserzioni.

 Gli use case sono troppo complessi…? Compilo ed eseguo al volo…

Un approccio estremamente più semplice e immediato per verificare il codice potrebbe essere quello di crearsi una Console Application, copiare/incollare il codice e testare il codice dentro la console.

Questo ha però dei tempi tecnici necessari come creare la solution, compilare e andare in debug. Questo comporta una risposta psicologica negativa: il test si fa solo quando serve veramente. Il vantaggio ci sarebbe ma per esperienza, nonostante sia il modo più immediato, nessuno lo fa: ci sarà o no un motivo…?

Un test al volo è troppo macchinoso…? Viene in aiuto LINQPad!

Siamo finalmente arrivati al nostro incredibile “silver bullet”. LINQPad, oltre a verificare le query LINQ generate, consente di ispezionare il risultato di un qualunque snippet di codice .NET compilandolo “al volo” tramite CSharpCodeProvider.

Lo screenshot seguente mostra, nello scenario di test presentato all’inizio, un test “al volo” della validazione della mail in questione:

Expression

Abbiamo quindi “cablato” il test verificando che con email “test@gmail.com” il metodo Regex.Match restituisce Success == true. Possiamo inoltre navigare il risultato: ad esempio possiamo ispezionare la collezione Groups, ottenendo i relativi gruppi in modo molto semplice:
Navigate

Volendo potremmo immergere il test in più input significativi creando una piccola procedura come segue:
Program

Se il codice che stiamo copiando o incollando fa uso di librerie esterne (create da noi o scaricate) possiamo includerle in modo da farle riconoscere dal compilatore.

Nell’esempio precedente si nota l’extension method Dump che consente di mostrare a video il contenuto di una qualunque struttura consentone una semplice navigazione tra strutture complesse. Prendiamo come esempio un Dictionary… in LINQPad tramite Dump lo visualizzereste in questo modo, direi piuttosto comodo, no…??
Dictionary

Uso di LINQPad

LINQPad si presta benissimo ai seguenti usi:

  • test al volo di nostro codice copiato/incollato da VS
  • test al volo di API .NET come LINQ, PLINQ, RX, …
  • test degli stati intermedi del chaining (qui un approfondimento)
  • apprendimento tramite esperimenti di librerie del framework .NET o di terze parti scaricate tramite NuGet
  • ispezione e navigazione di strutture e relazioni
  • navigazione del database senza passare per continue JOIN
  • analisi del database da un più alto livello di astrazione
    • grazie a ORM supportati come EF
    • grazie a provider supportati come MSCRM, Sharepoint, Azure, DataMarket, OData, StreamInsight, …
  • utility di uso comune tramite extension method (qui alcuni extension method di terze parti)
    • query LINQ che generano file CSV
    • query LINQ che generano SQL complessi da inviare via mail

Prossimi passi…??

Questi i prossimi passi per guadagnare la cintura marrone in LINQPad:

  1. Scarica LINQPad
  2. Scrivi il tuo codice normalmente in Visual Studio
  3. Appena terminato un metodo, anche semplice, copialo in LINQPad
    1. Se il tuo metodo fa uso di strutture esterne copiale direttamente o, se troppe, includi la dll
  4. Esegui su un input di esempio e ispeziona tramite Dump che tutto sia ok
    1. Se necessario verifica anche i tempi di esecuzione riportati
    2. Se necessario salva il codice per un futuro uso nella cartella “My Queries
  5. Rendila un’abitudine nel tuo lavoro quotidiano

Se veramente diventa un’abitudine eviterai il 90% degli errori, abbattendo ore noiose e degradanti di debugging…

Questi i passi invece per guadagnare la cintura nera in LINQPad:

  1. nella posizione dove di solito apri SQL Server Management Studio, rimpiazzalo con LINQPad
  2. apri sempre LINQPad al suo posto e fai le query solo utilizzando LINQ
  3. dopo un periodo di iniziale lentezza rispetto a SSMS scoprine i vantaggi
  4. non eseguire più join… limitati a seguire i link
  5. non ripetere più le stesse query… crea extension method riusabili
  6. non ipotizzare le query LINQ prodotte… verifica il SQL e prendine consapevolezza

Sperimenta nuove API con LINQPad. Pronti per lo step??

Posted in .net, test | No Comments »