Komponententests für Enterprise-Apps

Hinweis

Dieses eBook wurde im Frühjahr 2017 veröffentlicht und wurde seitdem nicht aktualisiert. Es gibt viel in dem Buch, das wertvoll bleibt, aber einige der Materialien sind veraltet.

Mobile Apps haben eindeutige Probleme, bei denen desktop- und webbasierte Anwendungen sich keine Sorgen machen müssen. Mobile Benutzer unterscheiden sich von den geräten, die sie verwenden, durch Netzwerkkonnektivität, durch die Verfügbarkeit von Diensten und eine Reihe anderer Faktoren. Daher sollten mobile Apps getestet werden, da sie in der realen Welt verwendet werden, um ihre Qualität, Zuverlässigkeit und Leistung zu verbessern. Es gibt viele Arten von Tests, die für eine App durchgeführt werden sollten, einschließlich Komponententests, Integrationstests und Benutzeroberflächentests, wobei Komponententests die am häufigsten verwendete 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. Ziel ist es, zu überprüfen, ob jede Funktionseinheit erwartungsgemäß ausgeführt wird, sodass Fehler nicht in der gesamten App verteilt 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 wirken sich am stärksten auf die Codequalität aus, wenn sie ein integraler Bestandteil des Softwareentwicklungsworkflows ist. Sobald eine Methode geschrieben wurde, sollten Komponententests geschrieben werden, die das Verhalten der Methode als Reaktion auf Standard-, Grenz- und Falsche Fälle von Eingabedaten überprüfen und alle expliziten oder impliziten Annahmen des Codes überprüfen. Alternativ werden Komponententests mit testgesteuerter Entwicklung vor dem Code geschrieben. In diesem Szenario dienen Komponententests sowohl als Entwurfsdokumentation als auch als funktionale Spezifikationen.

Hinweis

Komponententests sind sehr effektiv gegen Regression – d. h. Funktionalität, die für die Arbeit, aber durch ein fehlerhaftes Update gestört wurde.

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

  • Der Anordnungsbereich der Komponententestmethode initialisiert Objekte und legt den Wert der Daten fest, die an die zu testende Methode übergeben werden.
  • Im Act-Abschnitt wird die methode aufgerufen, die mit den erforderlichen Argumenten getestet wird.
  • Der Abschnitt "Assert " überprüft, ob sich die Aktion der unter Test ausgeführten Methode wie erwartet verhält.

Nach 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 Typen, die bei Autofac registriert sind, 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 vom IOrderService Typ, den der Container aufgelöst, wenn es 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 Tests. Abbildung 10-1 veranschaulicht diese Beziehung.

Klassen, die die IOrderService-Schnittstelle implementieren

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

Mit diesem Ansatz kann das OrderService Objekt zur Laufzeit an die OrderDetailViewModel Klasse übergeben werden, und im Interesse der Testbarkeit kann die OrderMockService Klasse zur Testzeit an die OrderDetailViewModel Klasse übergeben werden. Der Hauptvorteil 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 aller anderen Klassen, und die gleichen Tools und Techniken , z. B. Komponententests und Modelle, können verwendet werden. Es gibt jedoch einige Muster, die typisch für Modell- und Ansichtsmodellklassen sind, die von bestimmten Komponententests 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 Tests, die immer wahr sind, die invariante Bedingungen testen.
  • Theorien sind Tests, die nur für eine bestimmte Datenmenge gelten.

Die Komponententests, die in der mobilen eShopOnContainers-App enthalten sind, sind Faktentests, sodass jede Komponententestmethode mit dem [Fact] Attribut versehen ist.

Hinweis

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

Testen asynchroner Funktionen

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 eine OrderMockService Instanz, die eine simulierte Version der OrderService Klasse ist, als Argument für den OrderDetailViewModel Konstruktor angegeben. Wenn dann die Methode des InitializeAsync Ansichtsmodells 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 Lambda-Ausdruck angegeben ist, 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 mit dem Namen OnItemTappedhat. 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.

Überprüfung testen

Es gibt zwei Aspekte zum Testen der Validierungsimplementierung: Testen, dass 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. Ziel ist es, zu überprüfen, ob jede Funktionseinheit erwartungsgemäß ausgeführt wird, sodass Fehler nicht in der gesamten App verteilt 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. Dies ermöglicht die Ausführung von Komponententests, ohne dass unübersichtlich 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.