Komponententests für Enterprise-Apps

Hinweis

Dieses E-Book wurde im Frühjahr 2017 veröffentlicht und seitdem nicht mehr aktualisiert. Es gibt vieles im Buch, das wertvoll bleibt, aber ein Teil des Materials ist veraltet.

Mobile Apps weisen einzigartige Probleme auf, über die desktop- und webbasierte Anwendungen sich keine Sorgen machen müssen. Mobile Benutzer unterscheiden sich je nach den verwendeten Geräten, nach Netzwerkkonnektivität, nach Der Verfügbarkeit von Diensten und einer Reihe anderer Faktoren. Daher sollten mobile Apps getestet werden, da sie in der Praxis verwendet werden, um ihre Qualität, Zuverlässigkeit und Leistung zu verbessern. Es gibt viele Arten von Tests, die für eine App ausgeführt werden sollten, einschließlich Komponententests, Integrationstests und Benutzeroberflächentests, wobei Komponententests die häufigste Form von Tests sind.

Bei einem Komponententest wird eine kleine Einheit der App (in der Regel eine Methode) vom Rest des Codes isoliert und überprüft, ob diese sich wie erwartet verhält. Ihr Ziel ist es, zu überprüfen, ob jede Funktionseinheit wie erwartet funktioniert, damit Fehler nicht in der gesamten App weitergegeben werden. Es ist effizienter, zu ermitteln, wo ein Fehler auftritt, als die Auswirkung eines Fehlers indirekt an einem sekundären Point of Failure zu beobachten.

Komponententests haben die größte Auswirkung auf die Codequalität, wenn sie ein integraler Bestandteil des Softwareentwicklungsworkflows sind. Sobald eine Methode geschrieben wurde, sollten Komponententests geschrieben werden, die das Verhalten der Methode als Reaktion auf Standard-, Begrenzungs- und falsche Fälle von Eingabedaten überprüfen und alle expliziten oder impliziten Annahmen des Codes überprüfen. Alternativ werden bei der testgesteuerten Entwicklung Komponententests vor dem Code geschrieben. In diesem Szenario dienen Komponententests sowohl als Entwurfsdokumentation als auch als Funktionsspezifikationen.

Hinweis

Komponententests sind sehr effektiv gegen Regression– d. h. Funktionen, die früher funktioniert haben, aber durch ein fehlerhaftes Update gestört wurden.

Komponententests verwenden in der Regel das Arrange-Act-Assert-Muster:

  • Der Abschnitt arrange der Komponententestmethode initialisiert Objekte und legt den Wert der Daten fest, die an die zu testde Methode übergeben werden.
  • Im Abschnitt act wird die zu test befindliche Methode mit den erforderlichen Argumenten aufgerufen.
  • Im Abschnitt assert wird überprüft, ob sich die Aktion der zu test befindlichen Methode wie erwartet verhält.

Mit diesem Muster wird sichergestellt, dass Komponententests lesbar und konsistent sind.

Abhängigkeitsinjektion und Komponententests

Einer der Gründe für die Einführung einer lose gekoppelten Architektur besteht darin, dass sie Komponententests vereinfacht. Einer der bei Autofac registrierten Typen ist die OrderService -Klasse. Das folgende Codebeispiel zeigt eine Gliederung dieser Klasse:

public class OrderDetailViewModel : ViewModelBase  
{  
    private IOrderService _ordersService;  

    public OrderDetailViewModel(IOrderService ordersService)  
    {  
        _ordersService = ordersService;  
    }  
    ...  
}

Die OrderDetailViewModel -Klasse hat eine Abhängigkeit IOrderService vom Typ, den der Container auflöst, wenn er ein OrderDetailViewModel -Objekt instanziiert. Anstatt jedoch ein OrderService Objekt zu erstellen, um die OrderDetailViewModel Klasse zu testen, ersetzen Sie das OrderService Objekt stattdessen durch ein Modell für die Zwecke der Tests. Abbildung 10-1 veranschaulicht diese Beziehung.

Klassen, die die IOrderService-Schnittstelle implementieren

Abbildung 10-1: Klassen, die die IOrderService-Schnittstelle implementieren

Dieser Ansatz ermöglicht es, das OrderService Objekt zur Laufzeit an die OrderDetailViewModel -Klasse zu übergeben, und im Interesse der Testbarkeit kann die OrderMockService Klasse zur Testzeit an die OrderDetailViewModel -Klasse übergeben werden. Der Standard Vorteil dieses Ansatzes besteht darin, dass Komponententests ausgeführt werden können, ohne dass unübersichtlich Ressourcen wie Webdienste oder Datenbanken erforderlich sind.

Testen von MVVM-Anwendungen

Das Testen von Modellen und Ansichtsmodellen aus MVVM-Anwendungen ist identisch mit dem Testen anderer Klassen, und es können dieselben Tools und Techniken wie Komponententests und -mocking verwendet werden. Es gibt jedoch einige Muster, die typisch für das Modellieren und Anzeigen von Modellklassen sind, die von bestimmten Komponententesttechniken profitieren können.

Tipp

Testen Sie mit jedem Komponententest eine Sache. Versuchen Sie nicht, mit einem Komponententest mehr als einen Aspekt des Verhaltens der Einheit zu überprüfen. Dies führt zu Tests, die schwer zu lesen und zu aktualisieren sind. Es kann auch zu Verwirrung bei der Interpretation eines Fehlers führen.

Die mobile eShopOnContainers-App führt Komponententests durch, die zwei verschiedene Arten von Komponententests unterstützen:

  • Fakten sind immer zutreffende Tests, die invariante Bedingungen testen.
  • Theorien sind Tests, die nur für einen bestimmten Satz von Daten gelten.

Die Komponententests, die in der mobilen eShopOnContainers-App enthalten sind, sind Faktentests, sodass jede Komponententestmethode mit dem [Fact] -Attribut ergänzt wird.

Hinweis

xUnit-Tests werden von einem Testrunner ausgeführt. Führen Sie zum Ausführen des Testrunners das Projekt eShopOnContainers.TestRunner für die erforderliche Plattform aus.

Testen der asynchronen Funktionalität

Bei der Implementierung des MVVM-Musters rufen Ansichtsmodelle in der Regel Vorgänge für Dienste auf (häufig asynchron). Tests für Code, der diese Vorgänge aufruft, verwenden in der Regel Pseudokomponenten als Ersatz für die tatsächlichen Dienste. Im folgenden Codebeispiel wird das Testen asynchroner Funktionen veranschaulicht, indem ein Pseudodienst in ein Ansichtsmodell übergeben wird:

[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);  
}

Dieser Komponententest überprüft, ob die Order-Eigenschaft der OrderDetailViewModel-Instanz einen Wert aufweist, nachdem die InitializeAsync-Methode aufgerufen wurde. Die InitializeAsync-Methode wird aufgerufen, wenn zur entsprechenden Ansicht des Ansichtsmodells navigiert wird. Weitere Informationen zur Navigation finden Sie unter Navigation.

Wenn die OrderDetailViewModel-Instanz erstellt wird, wird erwartet, dass eine OrderService-Instanz als Argument angegeben ist. OrderService ruft jedoch Daten aus einem Webdienst ab. Daher wird ein OrderMockService instance, bei dem es sich um eine Pseudoversion der OrderService -Klasse handelt, als Argument für den OrderDetailViewModel Konstruktor angegeben. Wenn dann die -Methode des Ansichtsmodells InitializeAsync aufgerufen wird, die Vorgänge aufruft IOrderService , werden simulierte Daten abgerufen, anstatt mit einem Webdienst zu kommunizieren.

Testen von INotifyPropertyChanged-Implementierungen

Durch die Implementierung der INotifyPropertyChanged-Schnittstelle können Ansichten auf Änderungen reagieren, die von Ansichtsmodellen und Modellen stammen. Diese Änderungen sind nicht auf Daten beschränkt, die in Steuerelementen angezeigt werden. Sie werden auch verwendet, um die Ansicht zu steuern, z. B. Ansichtsmodellzustände, die dazu führen, dass Animationen gestartet oder Steuerelemente deaktiviert werden.

Eigenschaften, die direkt vom Komponententest aktualisiert werden können, können getestet werden, indem ein Ereignishandler an das PropertyChanged-Ereignis angefügt und überprüft wird, ob das Ereignis ausgelöst wird, nachdem ein neuer Wert für die Eigenschaft festgelegt wurde. Das folgende Codebeispiel zeigt einen solchen Test:

[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);  
}

Bei diesem Komponententest wird die InitializeAsync-Methode der OrderViewModel-Klasse aufgerufen, wodurch die Order-Eigenschaft aktualisiert wird. Der Komponententest wird bestanden, vorausgesetzt, das PropertyChanged-Ereignis wird für die Order-Eigenschaft ausgelöst.

Testen der nachrichtenbasierten Kommunikation

Für Ansichtsmodelle, die die MessagingCenter-Klasse für die Kommunikation zwischen lose gekoppelten Klassen verwenden, können wie im folgenden Codebeispiel veranschaulicht Komponententests durchgeführt werden, indem die Nachricht abonniert wird, die vom zu testenden Code gesendet wird:

[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);  
}

Mit diesem Komponententest wird überprüft, ob CatalogViewModel die AddProduct-Nachricht als Reaktion auf die Ausführung von AddCatalogItemCommand veröffentlicht wird. Da die MessagingCenter-Klasse Multicastnachrichtenabonnements unterstützt, kann der Komponententest die AddProduct-Nachricht abonnieren und einen Rückrufdelegat als Reaktion auf den Empfang ausführen. Dieser Rückrufdelegat, der als Lambdaausdruck angegeben wird, legt ein boolean Feld fest, das von der Assert -Anweisung verwendet wird, um das Verhalten des Tests zu überprüfen.

Testen der Ausnahmebehandlung

Komponententests können wie im folgenden Codebeispiel gezeigt auch so geschrieben werden, dass überprüft wird, ob bestimmte Ausnahmen für ungültige Aktionen oder Eingaben ausgelöst werden:

[Fact]  
public void InvalidEventNameShouldThrowArgumentExceptionText()  
{  
    var behavior = new MockEventToCommandBehavior  
    {  
        EventName = "OnItemTapped"  
    };  
    var listView = new ListView();  

    Assert.Throws<ArgumentException>(() => listView.Behaviors.Add(behavior));  
}

Dieser Komponententest löst eine Ausnahme aus, da das ListView Steuerelement kein Ereignis namens aufweist OnItemTapped. Die Assert.Throws<T>-Methode ist eine generische Methode, wobei T der Typ der erwarteten Ausnahme ist. Das an die Assert.Throws<T>-Methode übergebene Argument ist ein Lambdaausdruck, der die Ausnahme auslöst. Daher wird der Komponententest bestanden, sofern der Lambdaausdruck eine ArgumentException auslöst.

Tipp

Vermeiden Sie Komponententests, die Ausnahmemeldungszeichenfolgen untersuchen. Ausnahmemeldungszeichenfolgen können sich im Laufe der Zeit ändern, sodass Komponententests, die von deren Vorhandensein abhängen, als anfällig angesehen werden.

Testen der Überprüfung

Es gibt zwei Aspekte beim Testen der Validierungsimplementierung: Testen, ob alle Validierungsregeln ordnungsgemäß implementiert sind, und Tests, die die ValidatableObject<T> Klasse wie erwartet ausführt.

Validierungslogik ist in der Regel einfach zu testen, da es sich normalerweise um einen eigenständigen Prozess handelt, bei dem die Ausgabe von der Eingabe abhängt. Es sollten wie im folgenden Codebeispiel veranschaulicht Tests zu den Ergebnissen des Aufrufs der Validate-Methode für jede Eigenschaft mit mindestens einer zugeordneten Validierungsregel durchgeführt werden:

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

    bool isValid = mockViewModel.Validate();  

    Assert.True(isValid);  
}

Bei diesem Komponententest wird überprüft, ob die Validierung erfolgreich ist, wenn die beiden ValidatableObject<T>-Eigenschaften in der MockViewModel-Instanz beide Daten enthalten.

Neben der Überprüfung auf eine erfolgreiche Validierung sollten Validierungskomponententests auch die Werte der Eigenschaften Value, IsValid und Errors jeder ValidatableObject<T>-Instanz überprüfen, um zu ermitteln, ob die Klasse wie erwartet funktioniert. Im folgenden Codebeispiel wird ein Komponententest veranschaulicht, der diesen Vorgang ausführt:

[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);  
}

Dieser Komponententest überprüft, ob bei der Validierung ein Fehler auftritt, wenn die Surname-Eigenschaft von MockViewModel keine Daten enthält, und ob die Eigenschaften Value, IsValid und Errors jeder ValidatableObject<T>-Instanz ordnungsgemäß festgelegt sind.

Zusammenfassung

Bei einem Komponententest wird eine kleine Einheit der App (in der Regel eine Methode) vom Rest des Codes isoliert und überprüft, ob diese sich wie erwartet verhält. Ihr Ziel ist es, zu überprüfen, ob jede Funktionseinheit wie erwartet funktioniert, damit Fehler nicht in der gesamten App weitergegeben werden.

Das Verhalten eines zu testenden Objekts kann isoliert werden, indem abhängige Objekte durch Pseudoobjekte ersetzt werden, die das Verhalten der abhängigen Objekte simulieren. Dadurch können Komponententests ausgeführt werden, ohne dass sperrige Ressourcen wie Webdienste oder Datenbanken erforderlich sind.

Das Testen von Modellen und Ansichtsmodellen aus MVVM-Anwendungen ist identisch mit dem Testen anderer Klassen, und die gleichen Tools und Techniken können verwendet werden.