Freigeben über


Komponententest

Tipp

Diese Inhalte sind ein Auszug aus dem eBook „Enterprise Application Patterns Using .NET MAUI“, verfügbar unter .NET Docs oder als kostenlos herunterladbare PDF-Datei, die offline gelesen werden kann.

Enterprise Application Patterns Using .NET MAUI (Miniaturansicht des E-Book-Deckblatts)

Multiplattform-Apps haben Probleme, die sowohl Desktop- als auch webbasierten Anwendungen ähneln. Mobilbenutzer*innen unterscheiden sich je nach Gerät, Netzwerkkonnektivität, Verfügbarkeit von Diensten und verschiedenen anderen Faktoren. Daher sollten Multi-Plattform-Apps getestet werden, wie sie in der Realität tatsächlich verwendet werden, um ihre Qualität, Zuverlässigkeit und Leistung zu verbessern. Viele Arten von Tests sollten für eine App durchgeführt werden, einschließlich Komponententests, Integrationstests und Benutzeroberflächentests. Komponententests sind die am häufigsten verwendete Form und wesentlich für die Erstellung qualitativ hochwertiger Anwendungen.

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 wie erwartet ausgeführt wird, sodass Fehler nicht in der gesamten App auftreten. Es ist effizienter, einen Fehler genau dort zu erkennen, wo er auftritt, anstatt die Auswirkung des Fehlers indirekt an einer sekundären Fehlerstelle zu beobachten.

Komponententests wirken sich am wesentlichsten auf die Codequalität aus, wenn sie ein integraler Bestandteil des Softwareentwicklungsworkflows ist. Komponententests können als Entwurfsdokumentation und funktionale Spezifikationen für eine Anwendung fungieren. Sobald eine Methode geschrieben wurde, sollten Komponententests geschrieben werden, die das Verhalten der Methode als Reaktion auf Standard-, Begrenzungs- und falsche Eingabedatenfälle überprüfen und explizite oder implizite Annahmen des Codes überprüfen. Alternativ werden Komponententests mit testgesteuerter Entwicklung vor dem Code geschrieben. Weitere Informationen zur testgesteuerten Entwicklung und zur Implementierung finden Sie unter Exemplarische Vorgehensweise: Testgesteuerte Entwicklung mit dem Test-Explorer.

Hinweis

Komponententests sind sehr effektiv gegen Regression. Damit sind Funktionen gemeint, die funktioniert haben, aber durch ein fehlerhaftes Update gestört wurden.

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

Schritt BESCHREIBUNG
Anordnen In diesem Schritt werden Objekte initialisiert und der Wert der Daten festgelegt, die an die zu testende Methode übergeben werden.
Reagieren Hier wird die zu testende Methode mit den erforderlichen Argumenten abgerufen.
Assert In diesem Schritt wird überprüft, ob die Aktion der zu testenden Methode wie erwartet funktioniert.

Dieses Muster stellt sicher, dass Komponententests lesbar, selbstbeschreibend 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 beim Abhängigkeitsinjektionsdienst registriert sind, ist die IAppEnvironmentService-Schnittstelle. Das folgende Codebeispiel zeigt eine Gliederung dieser Klasse:

public class OrderDetailViewModel : ViewModelBase
{
    private IAppEnvironmentService _appEnvironmentService;

    public OrderDetailViewModel(
        IAppEnvironmentService appEnvironmentService,
        IDialogService dialogService, INavigationService navigationService, ISettingsService settingsService)
        : base(dialogService, navigationService, settingsService)
    {
        _appEnvironmentService = appEnvironmentService;
    }
}

Die OrderDetailViewModel-Klasse weist eine Abhängigkeit vom IAppEnvironmentService-Typ auf, die der Abhängigkeitsinjektionscontainer auflöst, wenn er ein OrderDetailViewModel-Objekt instanziiert. Anstatt jedoch ein IAppEnvironmentService-Objekt zu erstellen, das echte Server, Geräte und Konfigurationen für Komponententests der OrderDetailViewModel-Klasse verwendet, ersetzen Sie für die Zwecke der Tests stattdessen das IAppEnvironmentService-Objekt durch ein Pseudoobjekt. Ein Pseudoobjekt ist ein Objekt, das dieselbe Signatur eines Objekts oder einer Schnittstelle aufweist, aber auf eine bestimmte Weise erstellt wird, um Komponententests zu unterstützen. Es wird häufig mit Abhängigkeitsinjektion verwendet, um bestimmte Schnittstellenimplementierungen zum Testen verschiedener Daten- und Workflowszenarios bereitzustellen.

Mit diesem Ansatz kann das IAppEnvironmentService-Objekt zur Laufzeit an die OrderDetailViewModel-Klasse übergeben werden, und im Sinne der Testbarkeit kann während des Tests eine Pseudoklasse an die OrderDetailViewModel-Klasse übergeben werden. Der Hauptvorteil dieses Ansatzes besteht darin, dass Komponententests ausgeführt werden können, ohne dass umfangreiche Ressourcen wie Runtimeplattformfeatures, Webdienste oder Datenbanken erforderlich sind.

Testen von MVVM-Anwendungen

Das Testen und Modellen und Ansichtsmodellen aus MVVM-Anwendungen (Model View ViewModel) ist identisch mit dem Testen anderer Klassen. Zudem werden dabei dieselben Tools und Techniken verwendet, die auch Features wie Komponententests und Simulationen umfassen. Einige Muster, die für Modell- und Ansichtsmodellklassen typisch sind, können jedoch von bestimmten Komponententesttechniken profitieren.

Tipp

Testen Sie mit jedem Komponententest eine Sache. Da die Komplexität eines Tests steigt, wird die Überprüfung dieses Tests erschwert. Sie können sicherstellen, dass die Tests wiederholbar und isoliert sind sowie eine kürzere Ausführungszeit haben, indem Sie einen Komponententest auf ein einzelnes Problem beschränken. Weitere Best Practices finden Sie unter Best Practices für Komponententests mit .NET.

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 Multi-Plattform-App „eShop“ verwendet MSTest zum Durchführen von Komponententests. Dieses Tool unterstützt zwei verschiedene Arten von Komponententests:

Testtyp attribute Beschreibung
TestMethod TestMethod Definiert die auszuführende Testmethode.
DataSource DataSource Tests, die nur für einen bestimmten Teil der Daten „true“ sind.

Die Komponententests im Rahmen der Multi-Plattform-App „eShop“ sind TestMethod, sodass jede Komponententestmethode mit dem TestMethod-Attribut versehen ist. Zusätzlich zu MSTest stehen mehrere weitere Testframeworks zur Verfügung, darunter NUnit und xUnit.

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:

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

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 IOrderService-Instanz als Argument angegeben ist. OrderService ruft jedoch Daten aus einem Webdienst ab. Daher wird eine OrderMockService-Instanz (Pseudoversion der OrderService-Klasse) als Argument für den OrderDetailViewModel-Konstruktor angegeben. Anschließend werden simulierte Daten abgerufen, anstatt mit einem Webdienst zu kommunizieren, wenn die InitializeAsync-Methode des Ansichtsmodells aufgerufen wird, die IOrderService-Vorgänge verwendet.

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. Ansichtsmodellstatus, die dazu führen, dass Animationen gestartet werden 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:

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

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:

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

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 ist, legt ein boolesches 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:

[TestMethod]
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 OnItemTapped aufweist. 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 zum Testen der Validierungsimplementierung: Testen der ordnungsgemäßen Implementierung aller Validierungsregeln und Testen auf die erwartete Ausführung der ValidatableObject<T>-Klasse.

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:

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

    var isValid = mockViewModel.Validate();

    Assert.IsTrue(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:

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

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 wie erwartet ausgeführt wird, sodass Fehler nicht in der gesamten App auftreten.

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. Auf diese Weise können Komponententests durchgeführt werden, ohne dass umfangreiche Ressourcen wie Runtimeplattformfeatures, 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.