Unit Testing delle app aziendali
Nota
Questo eBook è stato pubblicato nella primavera del 2017 e non è stato aggiornato da allora. C'è molto nel libro che rimane prezioso, ma alcuni dei materiali sono obsoleti.
Le app per dispositivi mobili presentano problemi univoci che le applicazioni desktop e basate sul Web non devono preoccuparsi. Gli utenti per dispositivi mobili variano in base ai dispositivi che usano, in base alla connettività di rete, alla disponibilità dei servizi e a una serie di altri fattori. Pertanto, le app per dispositivi mobili devono essere testate perché verranno usate nel mondo reale per migliorare la qualità, l'affidabilità e le prestazioni. Esistono molti tipi di test che devono essere eseguiti in un'app, tra cui unit test, test di integrazione e test dell'interfaccia utente, con unit test che costituiscono la forma più comune di test.
Uno unit test accetta una piccola unità dell'app, in genere un metodo, lo isola dal resto del codice e verifica che si comporti come previsto. L'obiettivo è verificare che ogni unità di funzionalità funzioni come previsto, in modo che gli errori non vengano propagati in tutta l'app. Rilevare un bug in cui si verifica è più efficiente osservare l'effetto di un bug indirettamente in un punto secondario di errore.
Gli unit test hanno il massimo effetto sulla qualità del codice quando è parte integrante del flusso di lavoro di sviluppo software. Non appena è stato scritto un metodo, gli unit test devono essere scritti che verificano il comportamento del metodo in risposta a casi standard, limite e non corretti dei dati di input e che controllano eventuali presupposti espliciti o impliciti effettuati dal codice. In alternativa, con lo sviluppo guidato dai test, gli unit test vengono scritti prima del codice. In questo scenario, gli unit test fungono da documentazione di progettazione e specifiche funzionali.
Nota
Gli unit test sono molto efficaci rispetto alla regressione, ovvero funzionalità usate per funzionare ma che sono state disturbate da un aggiornamento difettoso.
Gli unit test usano in genere il modello arrange-act-assert:
- La sezione arrange del metodo di unit test inizializza gli oggetti e imposta il valore dei dati passati al metodo sottoposto a test.
- La sezione act richiama il metodo sottoposto a test con gli argomenti obbligatori.
- La sezione assert verifica che l'azione del metodo sottoposto a test si comporti come previsto.
Seguendo questo modello si garantisce che gli unit test siano leggibili e coerenti.
Inserimento delle dipendenze e unit test
Una delle motivazioni per l'adozione di un'architettura ad accoppiamento libero consiste nel facilitare gli unit test. Uno dei tipi registrati con Autofac è la OrderService
classe . Il codice seguente ne è un esempio:
public class OrderDetailViewModel : ViewModelBase
{
private IOrderService _ordersService;
public OrderDetailViewModel(IOrderService ordersService)
{
_ordersService = ordersService;
}
...
}
La OrderDetailViewModel
classe ha una dipendenza dal IOrderService
tipo che il contenitore risolve quando crea un'istanza di un OrderDetailViewModel
oggetto. Tuttavia, anziché creare un OrderService
oggetto per eseguire unit test della OrderDetailViewModel
classe, sostituire invece l'oggetto OrderService
con una simulazione ai fini dei test. La figura 10-1 illustra questa relazione.
Figura 10-1: Classi che implementano l'interfaccia IOrderService
Questo approccio consente di passare l'oggetto OrderService
alla OrderDetailViewModel
classe in fase di esecuzione e, in base agli interessi della verificabilità, consente di passare la OrderMockService
classe alla OrderDetailViewModel
classe in fase di test. Il vantaggio principale di questo approccio è che consente l'esecuzione di unit test senza richiedere risorse difficili, ad esempio servizi Web o database.
Test di applicazioni MVVM
I modelli di test e i modelli di visualizzazione dalle applicazioni MVVM sono identici ai test di qualsiasi altra classe e agli stessi strumenti e tecniche, ad esempio unit test e simulazione, possono essere usati. Esistono tuttavia alcuni modelli tipici per modellare e visualizzare le classi del modello, che possono trarre vantaggio da tecniche di unit test specifiche.
Suggerimento
Testare una cosa con ogni unit test. Non essere tentati di eseguire un esercizio di unit test più di un aspetto del comportamento dell'unità. In questo modo si verificano test difficili da leggere e aggiornare. Può anche causare confusione durante l'interpretazione di un errore.
L'app per dispositivi mobili eShopOnContainers esegue unit test, che supporta due diversi tipi di unit test:
- I fatti sono test che sono sempre veri, che testano condizioni invarianti.
- Le teorie sono test che sono veri solo per un determinato set di dati.
Gli unit test inclusi nell'app per dispositivi mobili eShopOnContainers sono test dei fatti e quindi ogni metodo di unit test viene decorato con l'attributo [Fact]
.
Nota
I test xUnit vengono eseguiti da un runner di test. Per eseguire il test runner, eseguire il progetto eShopOnContainers.TestRunner per la piattaforma richiesta.
Test della funzionalità asincrona
Quando si implementa il modello MVVM, i modelli di visualizzazione in genere richiamano operazioni sui servizi, spesso in modo asincrono. Verifica il codice che richiama queste operazioni usa in genere fittizi come sostituzioni per i servizi effettivi. L'esempio di codice seguente illustra il test delle funzionalità asincrone passando un servizio fittizio in un modello di visualizzazione:
[Fact]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.NotNull(orderViewModel.Order);
}
Questo unit test verifica che la proprietà Order
dell'istanza OrderDetailViewModel
abbia un valore dopo che il metodo InitializeAsync
è stato richiamato. Il metodo InitializeAsync
viene richiamato quando si passa alla visualizzazione corrispondente del modello di visualizzazione. Per informazioni sullo spostamento tra le pagine, vedere Navigazione.
Quando l'istanza OrderDetailViewModel
viene creata, si prevede che un'istanza OrderService
venga specificata come argomento. Tuttavia, OrderService
recupera i dati da un servizio Web. Pertanto, un'istanza OrderMockService
, che è una versione fittizia della OrderService
classe, viene specificata come argomento per il OrderDetailViewModel
costruttore. Quindi, quando viene richiamato il metodo del modello di InitializeAsync
visualizzazione, che richiama IOrderService
le operazioni, vengono recuperati dati fittizi anziché comunicare con un servizio Web.
Test delle implementazioni di INotifyPropertyChanged
L'implementazione dell'interfaccia INotifyPropertyChanged
consente alle visualizzazioni di reagire alle modifiche provenienti da modelli e modelli di visualizzazione. Queste modifiche non sono limitate ai dati visualizzati nei controlli, ma vengono usati anche per controllare la visualizzazione, ad esempio gli stati del modello di visualizzazione che causano l'avvio delle animazioni o i controlli da disabilitare.
Le proprietà che possono essere aggiornate direttamente dallo unit test possono essere testate associando un gestore eventi all'evento PropertyChanged
e verificando se l'evento viene generato dopo aver impostato un nuovo valore per la proprietà. Nell'esempio di codice riportato di seguito viene illustrato un test di questo tipo:
[Fact]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
bool invoked = false;
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
orderViewModel.PropertyChanged += (sender, e) =>
{
if (e.PropertyName.Equals("Order"))
invoked = true;
};
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
Assert.True(invoked);
}
Questo unit test richiama il metodo InitializeAsync
della classe OrderViewModel
, che determina l'aggiornamento della relativa proprietà Order
. Lo unit test verrà superato, a condizione che l'evento PropertyChanged
venga generato per la proprietà Order
.
Test della comunicazione basata su messaggi
I modelli di visualizzazione che usano la classe MessagingCenter
per comunicare tra classi ad accoppiamento libero possono essere sottoposti a unit test sottoscrivendo il messaggio inviato dal codice sottoposto a test, come illustrato nell'esempio di codice seguente:
[Fact]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
bool messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
Xamarin.Forms.MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.True(messageReceived);
}
Questo unit test verifica che CatalogViewModel
pubblica il messaggio AddProduct
in risposta all'esecuzione AddCatalogItemCommand
. Poiché la classe MessagingCenter
supporta sottoscrizioni di messaggi multicast, lo unit test può sottoscrivere il messaggio AddProduct
ed eseguire un delegato di callback in risposta alla ricezione. Questo delegato di callback, specificato come espressione lambda, imposta un boolean
campo utilizzato dall'istruzione Assert
per verificare il comportamento del test.
Test della gestione delle eccezioni
Gli unit test possono anche essere scritti per verificare che vengano generate eccezioni specifiche per azioni o input non validi, come illustrato nell'esempio di codice seguente:
[Fact]
public void InvalidEventNameShouldThrowArgumentExceptionText()
{
var behavior = new MockEventToCommandBehavior
{
EventName = "OnItemTapped"
};
var listView = new ListView();
Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));
}
Questo unit test genererà un'eccezione, perché il ListView
controllo non dispone di un evento denominato OnItemTapped
. Il metodo Assert.Throws<T>
è un metodo generico in cui T
è il tipo dell'eccezione prevista. L'argomento passato al metodo Assert.Throws<T>
è un'espressione lambda che genererà l'eccezione. Pertanto, lo unit test passerà a condizione che l'espressione lambda generi un oggetto ArgumentException
.
Suggerimento
Evitare di scrivere unit test che esaminano le stringhe dei messaggi di eccezione. Le stringhe dei messaggi di eccezione possono cambiare nel tempo e quindi gli unit test che si basano sulla loro presenza vengono considerati fragili.
Test della convalida
Esistono due aspetti per testare l'implementazione della convalida: il test che tutte le regole di convalida vengono implementate correttamente e il test eseguito dalla ValidatableObject<T>
classe come previsto.
La logica di convalida è in genere semplice da testare, perché in genere è un processo autonomo in cui l'output dipende dall'input. Devono essere presenti test sui risultati della chiamata del metodo Validate
su ogni proprietà con almeno una regola di convalida associata, come illustrato nell'esempio di codice seguente:
[Fact]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
bool isValid = mockViewModel.Validate();
Assert.True(isValid);
}
Questo unit test verifica che la convalida abbia esito positivo quando le due proprietà ValidatableObject<T>
nell'istanza MockViewModel
hanno entrambi dati.
Oltre a verificare che la convalida abbia esito positivo, gli unit test di convalida devono anche controllare i valori della proprietà Value
, IsValid
e Errors
di ogni istanza ValidatableObject<T>
, per verificare che la classe venga eseguita come previsto. L'esempio di codice seguente illustra uno unit test che esegue questa operazione:
[Fact]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.False(isValid);
Assert.NotNull(mockViewModel.Forename.Value);
Assert.Null(mockViewModel.Surname.Value);
Assert.True(mockViewModel.Forename.IsValid);
Assert.False(mockViewModel.Surname.IsValid);
Assert.Empty(mockViewModel.Forename.Errors);
Assert.NotEmpty(mockViewModel.Surname.Errors);
}
Questo unit test verifica che la convalida abbia esito negativo quando la proprietà Surname
di MockViewModel
non dispone di dati e la proprietà Value
, IsValid
e Errors
di ogni istanza ValidatableObject<T>
sono impostate correttamente.
Riepilogo
Uno unit test accetta una piccola unità dell'app, in genere un metodo, lo isola dal resto del codice e verifica che si comporti come previsto. L'obiettivo è verificare che ogni unità di funzionalità funzioni come previsto, in modo che gli errori non vengano propagati in tutta l'app.
Il comportamento di un oggetto sottoposto a test può essere isolato sostituendo oggetti dipendenti con oggetti fittizi che simulano il comportamento degli oggetti dipendenti. In questo modo, gli unit test possono essere eseguiti senza richiedere risorse difficili, ad esempio servizi Web o database.
I modelli di test e i modelli di visualizzazione dalle applicazioni MVVM sono identici ai test di qualsiasi altra classe e possono essere usati gli stessi strumenti e tecniche.