Nelle scorse puntate ho parlato solo unit test che riguardano genericamente delle classi: ma quando si scrive software dotato di UI che valenza possono avere questi tipi di test ?
Ovviamente se il software è ben strutturato esisteranno delle classi di servizio atte a implementare logiche di funzionamento e che saranno usate dalla logica di funzionamento della UI: in tale ambito gli unit test possono essere utilizzati per controllarne la logica di funzionamento.
Ma se le UI sono scritte seguendo il pattern MVVM ecco che è possibile è possibile anche fare anche di più.
Siccome la logica di funzionamento di una maschera è proposta dalla relativa classe che rappresenta la ViewModel, ecco che in pratica implementare degli unit test per questa nella pratica corrisponde ad eseguire dei test sul comportamento della UI stessa.
Dirò una cosa che a qualcuno potrebbe fare storcere il naso: io penso che la ragione principale per adottare il pattern MVVM per UI sia proprio quello di poter sottoporre i relativi ViewModel a unit test, in modo tale da eseguire in modo indiretto la verifica del corretto funzionamento che avranno le relative UI.
E’ importante però sottolineare che se si vuole scrivere con successo degli unit-test per un ViewModel occorre essere certi che questa sia stata implementata seguendo le “regole sacre” del pattern MVVM.
In particolare le seguenti.
> La classe non deve dipendere in alcun modo da elementi o istruzioni che coinvolgono elementi di UI (creazione di pagine, modifiche di stili, consumare eventi proposti dalla UI, visualizzazione di messaggi di warning/errrore/avviso)
> Non deve usare alcun metodo statico offerto da altre classi
> Quando occorre utilizzare usare servizi esterni occorre tassativamente usare i componenti coinvolti via interfaccia (cioè usare solo l’interfaccia, e non l’oggetto), che quindi saranno risolti in concrete object utilizzando il Dependency Injection (questa parte non dovrebbe avere più alcun segreto perchè espliticitata nelle scrose puntate della serie).
Nel seguito esporrò come implementare unit test in ambito Xamarin: come noto qui il pattern MVVM la fa da padrone.
Comunque i concetti generali espressi sono validi per qualsiasi applicativo dotato di UI e per il quale si è deciso di usare il pattern MVVM (per esempio XAML/WPF).
Il "navigato" Navigator
Per l’esposizione nel seguito utilizzerò la app Focac-Book, di cui ne abbiamo parlato molto in questo blog.
Si tratta di una app che permette di segnalare a una comunità di appassionati i posti migliori dove si mangia la focaccia genovese.
Rispetto alle versioni presentate in queste pagine ho apportato un po' di trasformazioni per renderla più utile per i ns scopi: in particolare ho eliminato ogni connessione al backend azure, al fine di levare ogni complessità che in questo frangente non era interessante e, anzi, potreva complicare l'esposizione.
Con questa revisione ogni item viene solo salvato in locale senza alcuna persistenza. Inoltre ho eseguito un refactoring accurato per implementare correttamente nonché compiutamente il pattern MVVM.
Come accennato affinché sia possibile facilmente eseguire degli unit test su una classe di ViewModel, occorre che questa segua pedestremente il pattern MVVM, e in particolare i punti esposti sopra.
Inizio subito a far notare che la parte più pelosa della faccenda è riuscire a disaccoppiare completamente il codice che implementa il ViewModel dalla UI: non solo è vietato dal pattern ma qualsiasi dipendenza tra queste entità prima o poi proporrà dei problemi implementativi.
Detto in modo più brutale se nel codice della classe che implementa la ViewModel è presente qualche elemento riconducibile a UI allora il pattern non è implementato nel modo corretto.
Questo disaccoppiamento introduce una grossa difficoltà: come implementare, per esempio, la logica di apertura di una nuova pagina ? Più brutalmente: come fare da dentro una classe che implementa un ViewModel ad aprire una nuova pagina ? Per esempio come aprire una pagina di dettaglio quando si seleziona un item da una ListView ? O anche come fare apparire una finestra di dialogo modale con scelta multipla (SI/NO) la cui risposta deve essere gestita dalla logica di funzionamento sempre dello stesso ViewModel ?
Evidenzio il problema in altri termini: all’interno della classe che implementa il ViewModel "si sa" quando aprire una nuova pagina, o anche si ha bisogno di una risposta fornita a una maschera modale per poter proseguire una logica di funzionamento, ma in nessun modo i due elementi devono essere in contatto.
Quindi per quanto detto sopra non è possibile agire con le solite istruzioni offerte da Xamarin (come era nelle vecchie versioni di Focac-Book).
Apertura nuova maschera
await Application.Current.MainPage.Navigation.PushAsync(new FocacciaPostEditPage(focaccePostselectedItem));
Apertura Maschera Modale
await Application.Current.MainPage.DisplayAlert("Aggiunta Focacceria", "Occorre compilare nome utente e luogo", "OK");
Come detto prima utilizzare questo tipo di istruzioni all’interno di di una classe ViewModel è contrarissimo ai principi del pattern poiché crea una dipendenza con elementi di UI.
Ma, detto tra noi, del pattern in realtà potremmo anche ampiamente impiparcene: se funziona cosa ci frega ??
Però, nella pratica, come si potrebbe scrivere uno unit-test di un metodo che contiene al suo interno questo tipo di istruzioni ?
Sarebbe almeno un’attività veramente molto difficoltosa, per non dire impossibile se per caso la maschera trattata fosse modale.
Ultimo ma non ultimo uno degli elementi fondanti del pattern MVVM dovrebbe essere che la stessa classe che implementa il ViewModel dovrebbe poter essere riutilizzata anche in altri ambiti, e invece usando le istruzioni sopra si è bloccati alle chiamate offerte da Xamarin.
Per risolvere questo problema esistono una miriade di soluzioni, non tutte facili o intuitive, e che spesso rischiano di complicare il codice: il metodo più semplice è quello di usare un cosiddetto “navigator”.
Tramite questo strumento è possibile permettere al ViewModel di creare nuove pagine senza aver nessuna dipendenza dal mondo UI stessa.
E’ possibile implementare un “navigator” per conto proprio (in linkografia dei link che permettono di scriverne uno per conto proprio, perfettamente funzionante) o anche usare una libreria esterna.
Per anni ho usato un navigator auto-prodotto realizzato in modo molto simile a quanto esposto nei citati link, e non ho mai avuto grossi problemi: solo in casi particolari l’utilizzo di metodi statici o riferimenti non lazy verso diversi oggetti, sopratutto pagine, evidenziavano un utilizzo importante e non ottimizzato della memoria. Ma a dire il vero nulla di cui non dormirci la notte.
Solo recentemente, un po' per pigrizia, mi sono piegato all’utilizzo di una libreria esterna, FreshMVVM, ottenendo anche in questo caso buone perfomance e dimenticandomi totalmente delle piccole criticità cui ho fatto riferimento.
Osservo che uno dei punti di forza di questo strumento è che è già stato pensato per gli unit test, e ciò esemplifica non poco la parte di scrittura dei test. Nel codice di Focac-Book ho quindi utilizzato proprio questo framework, poiché mi è sembrato il modo più intuitivo per risolvere i problemi citati in precedenza.
Linkografia
Informatica pressapochista - Un viaggio negli unit-test: Introduzione - Prima parte
Informatica pressapochista - Un viaggio negli unit-test: xUnit e dintorni – Seconda parte
Informatica pressapochista - Un viaggio negli unit-test: Fake class e dintorni - Terza parte
Informatica pressapochista - Un viaggio negli unit-test: Repository pattern - Quarta parte
Informatica pressapochista - Un viaggio negli unit-test: Non male le fake class.... – Quinta parte
Informatica pressapochista - Un viaggio negli unit-test: Un viaggio negli unit-test: Ci serve Moq ? – Sesta parte
Fabio Cozzolino's blog - A simple approach to navigation with Model-View-ViewModel in Xamarin.Forms
Patrick Allwood - Decoupling views and navigation Xamarin.Forms