Condividi tramite


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 . L'esempio di codice seguente illustra una struttura di questa classe:

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.

Classes that implement the IOrderService interface

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 Order proprietà dell'istanza OrderDetailViewModel abbia un valore dopo che il InitializeAsync metodo è stato richiamato. Il InitializeAsync metodo viene richiamato quando si passa alla visualizzazione corrispondente del modello di visualizzazione. Per altre informazioni sulla navigazione, vedere Navigazione.

Quando l'istanza viene creata, si prevede che un'istanza OrderDetailViewModelOrderService 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à . L'esempio di codice seguente mostra 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 della classe , che determina l'aggiornamento InitializeAsync della OrderViewModel relativa Order proprietà. Lo unit test verrà superato, a condizione che l'evento PropertyChanged venga generato per la Order proprietà .

Test della comunicazione basata 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:

[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 pubblica CatalogViewModel il messaggio in risposta all'esecuzione AddProductAddCatalogItemCommand . Poiché la MessagingCenter classe supporta sottoscrizioni di messaggi multicast, lo unit test può sottoscrivere il AddProduct messaggio 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 Assert.Throws<T> metodo è un metodo generico in cui T è il tipo dell'eccezione prevista. L'argomento passato al Assert.Throws<T> metodo è 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 Validate metodo 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 ValidatableObject<T> proprietà 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 Valueproprietà , IsValide Errors di ogni ValidatableObject<T> istanza, 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 Surname proprietà di non dispone di MockViewModel dati e la Valueproprietà , IsValide Errors di ogni ValidatableObject<T> istanza 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.