Programming Languages Hacks

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

  • Subscribe

  • Lettori

    I miei lettori abituali

  • Twitter

Come adottare i test nel mondo reale

Posted by Ricibald on September 25th, 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 :-)

Leave a Reply

You must be logged in to post a comment.