In questo articolo introduciamo l’argomento del testing delle applicazioni enterprise facendo una panoramica degli aspetti più importanti legati a questa vasta tematica. Si tratta di una panoramica sull‘argomento, nella quale però non mancheremo di richiamare alcuni aspetti importanti che svilupperemo in seguito.
Introduzione
Parlare di un argomento complesso come quello del testing di una applicazione enterprise non è cosa semplice e che sia possibile esaurire in poche righe. Le applicazioni enterprise e le tecnologie alla base di queste sono così evolute e complesse che le variabili in gioco diventano così numerose da rendere piuttosto complicato definire una strategia efficiente ed efficace per il test. Il test coinvolge numerosi aspetti legati alle funzionalità dei singoli componenti, dell’applicazione nel suo insieme piuttosto che alle performance e alla scalabilità. Più che scegliere un tool specifico o una metodologia piuttosto di un’altra è molto importante che l’approccio a una corretta strategia di test faccia parte del bagaglio di un team di sviluppo e che questa tematica sia tenuta presente dall’inizio e percorra tutto il ciclo di sviluppo sin dalle fasi iniziali. Indipendentemente dagli strumenti utilizzati, infatti, è molto importante che il test avvenga sin dalle prime fasi dello sviluppo e prosegua nei suoi diversi stadi fino al rilascio dell’applicazione.
Non c’è niente di più sbagliato che procedere allo sviluppo di gran parte dell’applicazione e poi in fase finale eseguire i test. In questo modo sarà quasi impossibile correggere impostazioni architetturali errate in tempi brevi e con risultati soddisfacenti. Certo, tutti sappiamo che la fase di test dovrebbe essere parallela a quella di sviluppo (anzi, è essa stessa parte dello sviluppo). Ma nella pratica quotidiana, se si deve tagliare su qualcosa (per mancanza di tempo o di altre risorse) spesso si “taglia” proprio sui test.
Inoltre sarebbe anche consigliabile per applicazioni che hanno una certa complessità architetturale realizzare una “proof of concept” verticale, che comprenda tutti i layer applicativi in modo da poter analizzare nelle fasi iniziali tutti i possibili problemi legati all’approccio architetturale scelto.
Le tipologie di test
Una delle cose importanti è stabilire quale sia l’obiettivo del test. La risposta è ovviamente molteplice in quanto dipende da cosa si va a testare e per verificare cosa. Diciamo che generalmente un test serve per verificare l’aderenza di quanto realizzato alle specifiche di progetto. Specifiche che possono essere di carattere funzionale e non funzionale. Il test di carattere funzionale è quello che viene fatto mettendosi dal punto di vista dell’utente finale. Ovvero si va a verificare se l’applicazione fa quello che ci si aspetta o meglio quello che il cliente si aspetta in base ai requisiti fissati.
I test non funzionali invece coinvolgono aspetti intrinseci all’applicazione quali la usabilità, le performance, la scalabilità, la modularità.
Queste categorie di test sono diverse tra di loro ma entrambe estremamente importanti dal momento che è fondamentale che una applicazione soddisfi le esigenze dell’utente finale ma è altresì importante che garantisca altre caratteristiche essenziali quali tempi di risposta accettabili e scalabilità.
Come è possibile intuire lo scenario è vasto e interessa tutte le figure coinvolte nella realizzazione di una applicazione enterprise. Dallo sviluppatore che avrà il compito di testare i componenti da lui realizzati, all’analista che dovrà garantire l’aderenza alle specifiche funzionali. In contesti più evoluti sarà possibile anche pensare ad un gruppo di quality assurance dedicato che provveda ad effettuare i test in base alle specifiche fornite.
Purtroppo nella realtà di tutti giorni non sempre il test è condotto secondo una strategia ben definita e non sempre è documentato a dovere con casi di test esaustivi. Molto spesso l’attività di test è condotta con un approccio piuttosto artigianale lasciando allo sviluppatore la responsabilità dello stesso. Ciò spesso porta ad accorgersi di bug di componenti, o ancora peggio di buchi di tipo funzionale, quando l’applicazione è già rilasciata, con le ovvie conseguenze del caso.
Va anche detto, ad onor del vero, che per quanto si possa pensare ad una strategia di testing corretta è praticamente impossibile pensare di azzerare tutti i possibili errori o malfunzionamenti di un sistema software. Sicuramente l’obiettivo di un test corretto è garantire che l’applicazione rilasciata sia stabile, scalabile, performante e aderente alle specifiche non che sia perfetta e senza bug.
Detto questo, qualunque sia la metodologia di sviluppo utilizzata, la cosa fondamentale è che il test faccia parte dal principio del ciclo di sviluppo del software e che sia condotto secondo una strategia definita. Le principali categorie di test possono essere racchiuse nelle seguenti: lo unit test (test di unità), gli integration test (test di integrazione), e i performance test (i test di carico che valutano la prestazione).
Test di unità
Come è facile intuire dal nome lo Unit Test è il test a più bassa granularità che possa essere eseguito. Quindi è fatto sulla più piccola unità del sistema che per i sistemi ad oggetti può essere una classe mentre per i sistemi procedurali è un programma o una routine. Lo Unit Test ha lo scopo di testare ad esempio i singoli metodi di una classe per verificare che questi si comportino nel modo corretto in base ai casi di test precedentemente scritti.
Teoria a parte molto spesso lo unit test non è condotto in maniera corretta per numerose ragioni. Spesso gli sviluppatori sono sotto pressione per rispettare tempi di consegna stringenti, spesso non hanno la formazione e le conoscenze adeguate, insomma, al di là delle belle teorie, lo unit test è fatto senza una metodologia precisa ed è completamente lasciato alla precisione del singolo. Molto spesso il test è eseguito mediante un metodo main() inserito nella classe e lanciato dallo sviluppatore durante la fase di sviluppo. La cosa però peggiore è il fatto che i test dei singoli componenti non sono rieseguiti a fronte di cambiamenti e questo, nel tempo, porta a codice sempre più inaffidabile e instabile.
La cosa migliore sarebbe quella di automatizzare i test dei componenti. Lo standard de facto nel mondo Java è JUnit, il celebre tool open-source per l’automazione del test di componente. Dare una descrizione dettagliata di JUnit non è lo scopo della discussione che è invece possibile trovare in numerosi testi [1]. JUnit è un tool molto semplice da usare, scritto in Java, e consente di scrivere unit test su insiemi di classi da poter eseguire in automatico e che forniscono un risultato immediato ed univoco. È possibile anche inserire il test con JUnit nelle procedure Ant che eseguono le build di progetto validando così il codice a ogni build costruita.
Eseguire il test con un tool tipo JUnit non porta solo il vantaggio di poter automatizzare la fase di test. L’abitudine a eseguire i test in maniera sistematica porta inevitabilmente anche a una maggiore attenzione nella scrittura corretta del codice e al rispetto delle buone pratiche di programmazione.
Test di integrazione
Mentre con lo unit test si verificano i singoli componenti del sistema, con il test di integrazione si verifica come i vari componenti del sistema interagiscono. La distinzione è più logica che pratica perche’ nulla vieta che un test di integrazione possa essere eseguito con lo stesso tool usato per lo unit test, come JUnit (e infatti, in questo senso, il nome del tool è “ingannevole” perche’ a tutt’oggi sono in molti a essere convinti che JUnit faccia solo ed esclusivamente test di unità). In realtà il test di integrazione avviene su componenti a più alto livello come ad esempio sulle interfacce esposte dal layer di business di un’applicazione, sulle interfacce dello strato di persistenza o sulle interfacce web.
Se, come visto nella serie, abbiamo realizzato la nostra applicazione in maniera corretta, ovvero facendo comunicare i diversi layer applicativi mediante interfacce e abbiamo usato un framework come Spring per la scrittura degli oggetti di business, allora sarà abbastanza semplice automatizzare anche il test di integrazione costruendo casi di test che a partire dai principali casi d’uso dell’applicazione vanno a eseguire i test sulle corrispondenti interfacce degli oggetti di business. Allo stesso modo si potranno testare le interazioni con il database eseguendo i test sulle interfacce degli oggetti DAO.
Discorso a parte va fatto per il test delle interfacce web. Le interfacce web sono estremamente difficili da testare in modo sistematico perche’ vi sono da tenere presenti numerosi fattori. Alcuni sono intrinseci ai browser come la possibilità di aprire più finestre e tab o la possibilità di usare il tasto back, altri alle modalità di utilizzo dell’utente.
Vi sono diversi tool che consentono di memorizzare percorsi di utilizzo di una interfaccia web e ripeterli in maniera automatizzata come ad esempio Selenium [3] disponibile anche come plugin di Firefox. Questi tool possono essere un ausilio ma la cosa veramente importante è realizzare l’applicazione, come più volte detto durante la serie, in modo che l’interfaccia web non esegua mai business logic, che in tal caso sarebbe ardua da testare, ma sia solo una vista del sistema che utilizza le interfacce esposte dal layer di business. In questo modo testare gli use case equivale dal punto di vista del test di integrazione a testare le interfacce dei business object.
Il test dell’interfaccia sarebbe quindi più che altro un test funzionale fatto dalla prospettiva dell’utente che può essere eseguito in maniera non automatizzata a partire dalle specifiche dei requisiti del sistema.
I test di carico
È bene accennare all’importanza dei test legati alle performance di un sistema, che hanno lo scopo di verificare come il sistema si adatti ad un carico crescente e quanto quindi sia scalabile, ovvero quanto garantisca tempi di risposta efficienti al crescere del carico.
I test di unità, di integrazione e funzionali ci garantiscono infatti che il sistema faccia quello che ci si aspetta che debba fare da un punto di vista dei requisiti funzionali. Ma questi test non garantiscono in alcun modo che il sistema si comporti bene sotto stress. I test di carico sono quindi estremamente importanti e andrebbero eseguiti non quando l’applicazione viene rilasciata ma all’inizio dello sviluppo come accennato in precedenza. La cosa migliore sarebbe costruire una singola funzionalità del sistema, completa di tutti i layer applicativi realizzati secondo l’architettura scelta, in modo da poter verificare all’inizio se le soluzioni adottate portano a colli di bottiglia o a problemi in situazioni di carico applicativo. Per l’esecuzione dei test di carico esistono numerosi tool commerciali e open. Una pagina in cui sono elencati molti di questi tool e in genere tool utilizzati per la quality assurance è riportata tra i riferimenti [4].
Con i test di carico è possibile simulare una popolazione virtuale di utenti che eseguono richieste simultanee all’applicazione secondo percorsi ben definiti e curve di carico selezionabili.
Eseguendo questi test è possibile avere informazioni precise sulla capacità del sistema di scalare e possono dare importanti indicazioni su come adattare non solo l’applicazione ma anche l’ambiente di esercizio nel quale l’applicazione andrà in esecuzione.
Conclusioni
Nel presente articolo abbiamo introdotto l’argomento del testing delle applicazioni enterprise. Argomento vasto e molto complesso che coinvolge aspetti legati alla architettura applicativa, alle specifiche funzionali, alle tecnologie. Per ognuna delle tipologie di test accennate nell’articolo sarebbe possibile scrivere non un singolo articolo ma un intero libro quindi è ovvio che quanto detto non possa che costituire semplicemente una introduzione all’argomento.
A parte però i dettagli che magari approfondiremo in successivi articoli la cosa fondamentale è avere l’approccio giusto al test. L’unico modo corretto è quello di considerare il test un elemento essenziale del ciclo di sviluppo da mettere in atto sin dalle prime fasi del progetto unitamente alla realizzazione dello stesso e non una appendice finale da praticare all’atto del rilascio.
Solo in quest’ottica, tool utilizzati a parte, il test potrà raggiungere gli obiettivi desiderati.
Riferimenti
[1] Petar Tahchiev, Felipe Leme, Vincent Massol, Gary Gregory, “JUnit in Action”, seconda edizione, Manning, 2008
[2] Rod Johnson, “Expert one-on-one. J2EE Design and Development”, Wrox Press, 2002
[3] Selenium
[4] Un elenco di tool per il testing e per la quality assurance
http://www.softwareqatest.com/qatweb1.html