Condividi tramite


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.

Enterprise Application Patterns Using .NET MAUI eBook cover thumbnail.

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. 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 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 di testing unità con .NET Core e .NET Standard.

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 eShopOnContainers usa xUnit per eseguire unit test, che supporta due diversi tipi di unit test:

Tipo di test Attributo Descrizione
Fatti Fact Test che sono sempre true, che testano condizioni invarianti.
Teorie Theory Test che sono true solo per un determinato set di dati.

Gli unit test inclusi nell'app multipiattaforma eShopOnContainers sono test dei fatti, quindi ogni metodo di unit test viene decorato con l'attributo Fact.

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()
{
    // 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.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 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:

[Fact]
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.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 delle comunicazioni basate 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()
{
    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.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 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:

[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 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:

[Fact]
public void CheckValidationPassesWhenBothPropertiesHaveDataTest()
{
    var mockViewModel = new MockViewModel();
    mockViewModel.Forename.Value = "John";
    mockViewModel.Surname.Value = "Smith";

    var 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, IsValide 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.