Unit test
Suggerimento
Questo contenuto è un estratto dell'eBook, Enterprise Application Patterns Using .NETMAUI, disponibile in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.
Le app multipiattaforma riscontrano problemi simili alle applicazioni desktop e basate sul Web. Gli utenti mobili variano in base ai dispositivi, alla connettività di rete, alla disponibilità dei servizi e a vari altri fattori. Di conseguenza, le app multipiattaforma devono essere testate come verrebbero usate nel mondo reale per migliorare la qualità, l'affidabilità e le prestazioni. Molti tipi di test devono essere eseguiti in un'app, tra cui unit test, test di integrazione e test dell'interfaccia utente. Gli unit test sono il formato più comune ed essenziale per la creazione di applicazioni di alta qualità.
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à venga eseguita come previsto, quindi gli errori non vengono propagati in tutta l'app. Il rilevamento di un bug in cui si verifica è più efficiente rispetto all'osservazione dell'effetto di un bug indirettamente in un punto secondario di errore.
Gli unit test offrono i risultati più significativi in relazione alla qualità del codice quando sono parte integrante del flusso di lavoro di sviluppo di software. Gli unit test possono fungere da documentazione di progettazione e specifiche funzionali per un'applicazione. Non appena è stato scritto un metodo, gli unit test devono essere scritti per verificare il comportamento del metodo in risposta a casi di dati di input standard, limite e non corretti e verificare eventuali presupposti espliciti o impliciti effettuati dal codice. In alternativa, con lo sviluppo basato su test, gli unit test vengono scritti prima del codice. Per altre informazioni sullo sviluppo basato su test e su come implementarlo, vedere Procedura dettagliata: Sviluppo basato su test con Esplora test.
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:
Procedi | Descrizione |
---|---|
Disponi | Inizializza oggetti e imposta il valore dei dati passati al metodo da testare. |
Azione | Richiama il metodo sottoposto a test con gli argomenti obbligatori. |
Assert | Verifica che l'azione del metodo da testare si comporti come previsto. |
Questo modello garantisce che gli unit test siano leggibili, autodescritti 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 il servizio di inserimento delle dipendenze è l'interfaccia IAppEnvironmentService
. Il codice seguente ne è un esempio:
public class OrderDetailViewModel : ViewModelBase
{
private IAppEnvironmentService _appEnvironmentService;
public OrderDetailViewModel(
IAppEnvironmentService appEnvironmentService,
IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
: base(dialogService, navigationService, settingsService)
{
_appEnvironmentService = appEnvironmentService;
}
}
La classe OrderDetailViewModel
ha una dipendenza dal tipo IAppEnvironmentService
, che il contenitore di inserimento delle dipendenze viene risolto quando crea un'istanza di un oggetto OrderDetailViewModel
. Tuttavia, anziché creare un oggetto IAppEnvironmentService
che utilizza server reali, dispositivi e configurazioni per eseguire unit test della classe OrderDetailViewModel
, sostituire invece l'oggetto IAppEnvironmentService
con un oggetto fittizio ai fini dei test. Un oggetto fittizio è uno che ha la stessa firma di un oggetto o di un'interfaccia, ma viene creato in modo specifico per semplificare il testing unità. Viene spesso usato con l'inserimento delle dipendenze per fornire implementazioni specifiche di interfacce per il test di diversi scenari di dati e flussi di lavoro.
Questo approccio consente di passare l'oggetto IAppEnvironmentService
alla classe OrderDetailViewModel
in fase di esecuzione e, in base all'interesse della verificabilità, consente di passare una classe fittizia alla classe OrderDetailViewModel
in fase di test. Il vantaggio principale di questo approccio è che consente l'esecuzione di unit test senza richiedere risorse difficili, ad esempio funzionalità della piattaforma di runtime, servizi Web o database.
Test di applicazioni MVVM
Il test di modelli e modelli di visualizzazione da applicazioni MVVM è identico a quello di qualsiasi altra classe e usa gli stessi strumenti e tecniche; sono incluse funzionalità come unit test e simulazione. Tuttavia, alcuni modelli tipici per modellare e visualizzare le classi di modelli possono trarre vantaggio da tecniche di unit test specifiche.
Suggerimento
Testare una cosa con ogni unit test. Man mano che la complessità di un test si espande, rende più difficile la verifica di tale test. Limitando uno unit test a un singolo problema, è possibile assicurarsi che i test siano più ripetibili, isolati e abbiano un tempo di esecuzione inferiore. Per altre procedure consigliate, vedere Procedure consigliate per unit test con .NET .
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 multipiattaforma eShop usa MSTest per eseguire unit test, che supporta due diversi tipi di unit test:
Tipo di test | Attributo | Descrizione |
---|---|---|
TestMethod | TestMethod |
Definisce il metodo di test effettivo da eseguire. |
Origine dati | DataSource |
Test che sono true solo per un determinato set di dati. |
Gli unit test inclusi nell'app multipiattaforma eShop sono TestMethod, quindi ogni metodo di unit test viene decorato con l'attributo TestMethod
. Oltre a MSTest sono disponibili diversi altri framework di test, tra cui NUnit e xUnit.
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:
[TestMethod]
public async Task OrderPropertyIsNotNullAfterViewModelInitializationTest()
{
// Arrange
var orderService = new OrderMockService();
var orderViewModel = new OrderDetailViewModel(orderService);
// Act
var order = await orderService.GetOrderAsync(1, GlobalSetting.Instance.AuthToken);
await orderViewModel.InitializeAsync(order);
// Assert
Assert.IsNotNull(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 IOrderService
venga specificata come argomento. Tuttavia, OrderService
recupera i dati da un servizio Web. Pertanto, un'istanza OrderMockService
, una versione fittizia della classe OrderService
, viene specificata come argomento per il costruttore OrderDetailViewModel
. I dati fittizi vengono quindi recuperati anziché comunicare con un servizio Web quando viene richiamato il metodo InitializeAsync
del modello di visualizzazione, che usa operazioni IOrderService
.
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 o i controlli delle animazioni.
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:
[TestMethod]
public async Task SettingOrderPropertyShouldRaisePropertyChanged()
{
var 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.IsTrue(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 delle comunicazioni basate su messaggi
I modelli di visualizzazione che usano la MessagingCenter
classe 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:
[TestMethod]
public void AddCatalogItemCommandSendsAddProductMessageTest()
{
var messageReceived = false;
var catalogService = new CatalogMockService();
var catalogViewModel = new CatalogViewModel(catalogService);
MessagingCenter.Subscribe<CatalogViewModel, CatalogItem>(
this, MessageKeys.AddProduct, (sender, arg) =>
{
messageReceived = true;
});
catalogViewModel.AddCatalogItemCommand.Execute(null);
Assert.IsTrue(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 campo booleano 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:
[TestMethod]
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 controllo ListView
non ha 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.
Convalida dei test
Esistono due aspetti per testare l'implementazione della convalida: test che tutte le regole di convalida siano implementate correttamente e test eseguite dalla classe ValidatableObject<T>
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:
[TestMethod]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
mockViewModel.Surname.Value = "Smith";
var isValid = mockViewModel.Validate();
Assert.IsTrue(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:
[TestMethod]
public void CheckValidationFailsWhenOnlyForenameHasDataTest()
{
var mockViewModel = new MockViewModel();
mockViewModel.Forename.Value = "John";
bool isValid = mockViewModel.Validate();
Assert.IsFalse(isValid);
Assert.IsNotNull(mockViewModel.Forename.Value);
Assert.IsNull(mockViewModel.Surname.Value);
Assert.IsTrue(mockViewModel.Forename.IsValid);
Assert.IsFalse(mockViewModel.Surname.IsValid);
Assert.AreEqual(mockViewModel.Forename.Errors.Count(), 0);
Assert.AreNotEqual(mockViewModel.Surname.Errors.Count(), 0);
}
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à venga eseguita come previsto, quindi gli errori non vengono 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. Ciò consente l'esecuzione di unit test senza richiedere risorse difficili, ad esempio funzionalità della piattaforma di runtime, 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.