Freigeben über


Testen ASP.NET Core MVC-Apps

Tipp

Dieser Inhalt ist ein Auszug aus dem eBook, Architect Modern Web Applications mit ASP.NET Core und Azure, verfügbar auf .NET Docs oder als kostenloses herunterladbares PDF, das offline gelesen werden kann.

Miniaturansicht des E-Books „Architect Modern Web Applications with ASP.NET Core and Azure“.

"Wenn Ihnen das Durchführen von Unit-Tests bei Ihrem Produkt nicht gefällt, werden Ihre Kunden es wahrscheinlich auch nicht gerne testen." _- Anonym-

Software von beliebiger Komplexität kann aufgrund von Änderungen auf unerwartete Weisen fehlschlagen. Daher ist es erforderlich, Anwendungen auf Änderungen zu testen, mit Ausnahme von unbedeutenden (bzw. weniger wichtigen) Anwendungen. Manuelle Tests sind die langsamste, unzuverlässigste und aufwendigste Möglichkeit zum Testen von Software. Wenn Anwendungen leider nicht testbar sind, kann dies die einzige Möglichkeit zum Testen sein. Anwendungen, die nach den in Kapitel 4 dargelegten architektonischen Grundsätzen geschrieben wurden, sollten weitgehend unit-testbar sein. ASP.NET Core-Anwendungen unterstützen automatisierte Integrations- und Funktionstests.

Arten von automatisierten Tests

Es gibt viele Arten von automatisierten Tests für Softwareanwendungen. Der einfachste, spezifischste Test ist der Komponententest. Integrationstests und Funktionstests sind etwas allgemeiner. Andere Arten von Tests, z. B. Benutzeroberflächentests, Belastungstests, Stresstests und Rauchtests, liegen außerhalb des Umfangs dieses Dokuments.

Komponententests

Ein Komponententest testet einen einzelnen Teil der Logik Ihrer Anwendung. Man kann diesen Test genauer beschreiben, indem man aufführt, was er nicht umfasst. Ein Komponententest überprüft nicht, wie Ihr Code mit Abhängigkeiten oder Infrastruktur interagiert. Dafür gibt es Integrationstests. Ein Modultest testet nicht das Framework, auf dem Ihr Code geschrieben wurde – Sie sollten davon ausgehen, dass es funktioniert, oder wenn Sie feststellen, dass es nicht funktioniert, einen Fehler melden und eine Problemumgehung programmieren. Ein Komponententest wird vollständig im Arbeitsspeicher und im Prozess ausgeführt. Sie kommuniziert nicht mit dem Dateisystem, dem Netzwerk oder einer Datenbank. Komponententests sollten nur Ihren Code testen.

Komponententests sollten aufgrund der Tatsache, dass sie nur eine einzelne Einheit Ihres Codes ohne externe Abhängigkeiten testen, extrem schnell ausgeführt werden. Daher sollten Sie testsammlungen von Hunderten von Komponententests in wenigen Sekunden ausführen können. Führen Sie sie häufig aus, idealerweise vor jedem Push an ein freigegebenes Quellcodeverwaltungs-Repository und sicherlich mit jedem automatisierten Build auf Ihrem Buildserver.

Integrationstests

Obwohl es eine gute Idee ist, Code zu kapseln, der mit Infrastruktur interagiert (z.B. Datenbanken und Dateisysteme), wird ein Teil des Codes übrig bleiben, den Sie wahrscheinlich testen möchten. Darüber hinaus sollten Sie sicherstellen, dass die Schichten Ihres Codes wie erwartet interagieren, wenn die Abhängigkeiten Ihrer Anwendung vollständig aufgelöst werden. Für diese Funktion sind Integrationstests verantwortlich. Integrationstests sind tendenziell langsamer und schwieriger einzurichten als Komponententests, da sie häufig von externen Abhängigkeiten und Infrastruktur abhängen. Daher sollten Sie Tests vermeiden, die mit Komponententests in Integrationstests getestet werden könnten. Wenn Sie ein bestimmtes Szenario mit einem Komponententest testen können, sollten Sie es mit einem Komponententest testen. Wenn dies nicht möglich ist, sollten Sie einen Integrationstest verwenden.

Integrationstests haben häufig komplexere Einrichtungs- und Zerreißverfahren als Komponententests. Beispielsweise benötigt ein Integrationstest, der einer tatsächlichen Datenbank entspricht, eine Möglichkeit, die Datenbank vor jeder Testausführung in einen bekannten Zustand zurückzugeben. Wenn neue Tests hinzugefügt werden und sich das Produktionsdatenbankschema weiterentwickelt, werden diese Testskripts tendenziell in Größe und Komplexität wachsen. In vielen großen Systemen ist es unpraktisch, vollständige Integrationstests auf Entwicklerarbeitsstationen auszuführen, bevor Änderungen an der gemeinsamen Quellcodeverwaltung überprüft werden. In diesen Fällen können Integrationstests auf einem Buildserver ausgeführt werden.

Funktionstests

Integrationstests werden aus Sicht des Entwicklers geschrieben, um zu überprüfen, ob einige Komponenten des Systems ordnungsgemäß zusammenarbeiten. Funktionale Tests werden aus sicht des Benutzers geschrieben und überprüfen die Richtigkeit des Systems basierend auf seinen Anforderungen. Der folgende Auszug bietet eine nützliche Analogie zum Nachdenken über Funktionstests im Vergleich zu Komponententests:

"Oft wird die Entwicklung eines Systems mit dem Gebäude eines Hauses ge liken. Obwohl diese Analogie nicht ganz richtig ist, können wir sie erweitern, um den Unterschied zwischen Einheits- und Funktionstests zu verstehen. Komponententests sind vergleichbar mit einem Bauinspektor, der die Baustelle eines Hauses besucht. Er konzentriert sich auf die verschiedenen internen Systeme des Hauses, das Fundament, die Konstruktion, elektrische Anlagen, sanitäre Einrichtungen, usw. Er stellt sicher (Tests), dass die Teile des Hauses ordnungsgemäß und sicher funktionieren, d. h. den Baucode erfüllen. Funktionale Tests in diesem Szenario sind analog zu dem Hausbesitzer, der dieselbe Baustelle besucht. Er geht davon aus, dass sich die internen Systeme angemessen verhalten werden, dass der Bauinspektor seine Aufgabe ausführt. Der Hausbesitzer konzentriert sich auf das, was es sein wird, in diesem Haus zu leben. Er beschäftigt sich damit, wie das Haus aussieht, sind die verschiedenen Zimmer eine komfortable Größe, passt das Haus den Bedürfnissen der Familie, sind die Fenster an einem guten Ort, um die Morgensonne zu fangen. Der Hausbesitzer führt funktionsbezogene Tests auf dem Haus durch. Er hat die Perspektive des Benutzers. Der Bauinspektor führt Komponententests am Haus durch. Er hat die Perspektive des Baumeisters."

Quelle: Komponententests im Vergleich zu funktionalen Tests

Ich möchte gerne sagen: "Als Entwickler schlagen wir auf zwei Arten fehl: Wir bauen das Ding falsch, oder wir bauen das falsche Ding." Komponententests stellen sicher, dass Sie das Richtige aufbauen; Funktionstests stellen sicher, dass Sie das richtige schaffen.

Da funktionale Tests auf Systemebene funktionieren, benötigen sie möglicherweise ein gewisses Maß an Benutzeroberflächenautomatisierung. Wie Integrationstests funktionieren sie in der Regel auch mit einer art Testinfrastruktur. Diese Aktivität macht sie langsamer und spröder als Komponenten- und Integrationstests. Sie sollten nur so viele funktionale Tests durchführen, wie nötig, um sicherzustellen, dass das System sich so verhält, wie die Benutzer es erwarten.

Pyramide testen

Martin Fowler schrieb über die Testpyramide, ein Beispiel hierfür ist in Abbildung 9-1 gezeigt.

Pyramide testen

Abbildung 9-1. Pyramide testen

Die verschiedenen Ebenen der Pyramide und ihre relativen Größen stellen unterschiedliche Arten von Tests dar und wie viele Sie für Ihre Anwendung schreiben sollten. Wie Sie sehen können, empfiehlt es sich, eine große Basis von Komponententests zu haben, die von einer kleineren Ebene von Integrationstests mit einer noch kleineren Ebene von Funktionstests unterstützt werden. Jede Ebene sollte idealerweise nur Tests enthalten, die nicht ausreichend auf einer unteren Ebene durchgeführt werden können. Beachten Sie die Testpyramide, wenn Sie entscheiden möchten, welche Art von Test Sie für ein bestimmtes Szenario benötigen.

Was zu testen ist

Ein häufiges Problem für Entwickler, die mit dem Schreiben automatisierter Tests unerfahren sind, ist die Entscheidungsfindung darüber, was getestet werden soll. Ein guter Ausgangspunkt ist das Testen der bedingten Logik. Überall dort, wo Sie eine Methode mit Verhalten haben, die sich basierend auf einer bedingten Anweisung (if-else, switch usw.) ändert, sollten Sie in der Lage sein, mindestens ein paar Tests zu erstellen, die das richtige Verhalten für bestimmte Bedingungen bestätigen. Wenn Ihr Code Fehlerbedingungen aufweist, ist es ratsam, mindestens einen Test für den "Happy Path" durch den Code (ohne Fehler) zu schreiben und mindestens einen Test für den "traurigen Pfad" (mit Fehlern oder atypischen Ergebnissen) zu schreiben, um zu bestätigen, dass sich Ihre Anwendung im Hinblick auf Fehler wie erwartet verhält. Versuchen Sie schließlich, sich darauf zu konzentrieren, Dinge zu testen, die fehlschlagen können, anstatt sich auf Metriken wie Codeabdeckung zu konzentrieren. Mehr Codeabdeckung ist besser als weniger, im Allgemeinen. Das Schreiben einiger zusätzlicher Tests für eine komplexe und geschäftskritische Methode ist in der Regel jedoch eine bessere Nutzung der Zeit als das Schreiben von Tests für automatische Eigenschaften, um die Test-Coverage-Metriken zu verbessern.

Organisieren von Testprojekten

Testprojekte können auf die für Sie beste Weise organisiert werden. Es ist ratsam, Tests nach Typ (Komponententest, Integrationstest) und nach dem Test (nach Projekt, nach Namespace) zu trennen. Ob diese Trennung aus Ordnern innerhalb eines einzelnen Testprojekts oder aus mehreren Testprojekten besteht, ist eine Entwurfsentscheidung. Ein Projekt ist am einfachsten, aber für große Projekte mit vielen Tests oder um unterschiedliche Testgruppen einfacher ausführen zu können, sollten Sie mehrere verschiedene Testprojekte haben. Viele Teams organisieren Testprojekte basierend auf dem Projekt, das sie testen, was für Anwendungen mit mehr als einigen Projekten zu einer großen Anzahl von Testprojekten führen kann, insbesondere, wenn Sie diese immer noch entsprechend der Art der Tests in jedem Projekt aufschlüsseln. Ein Kompromissansatz besteht darin, ein Projekt pro Testart pro Anwendung mit Ordnern innerhalb der Testprojekte zu verwenden, um das zu testende Projekt (und die Klasse) anzugeben.

Ein allgemeiner Ansatz besteht darin, die Anwendungsprojekte unter einem Ordner "src" und die Testprojekte der Anwendung unter einem parallelen Ordner "Tests" zu organisieren. Sie können übereinstimmende Lösungsordner in Visual Studio erstellen, wenn Sie diese Organisation hilfreich finden.

Testen der Organisation in Ihrer Lösung

Abbildung 9-2. Testen der Organisation in Ihrer Lösung

Sie können das von Ihnen bevorzugte Testframework verwenden. Das xUnit-Framework funktioniert gut und ist das, in dem alle ASP.NET Core- und EF Core-Tests geschrieben werden. Sie können ein xUnit-Testprojekt in Visual Studio mithilfe der Vorlage in Abbildung 9-3 oder über die CLI hinzufügen dotnet new xunit.

Hinzufügen eines xUnit-Testprojekts in Visual Studio

Abbildung 9-3. Hinzufügen eines xUnit-Testprojekts in Visual Studio

Benennen von Tests

Benennen Sie Ihre Tests einheitlich mit Namen, die angeben, was jeder Test tut. Ein Ansatz, mit dem ich große Erfolge hatte, besteht darin, Testklassen nach der Klasse und Methode zu benennen, die sie testen. Dieser Ansatz führt zu vielen kleinen Testklassen, macht aber deutlich, wofür jeder Test verantwortlich ist. Wenn der Name der Testklasse eingerichtet ist, um die zu testende Klasse und Methode zu identifizieren, kann der Name der Testmethode verwendet werden, um das getestete Verhalten anzugeben. Dieser Name sollte das erwartete Verhalten und alle Eingaben oder Annahmen enthalten, die dieses Verhalten liefern sollten. Einige Beispieltestnamen:

  • CatalogControllerGetImage.CallsImageServiceWithId

  • CatalogControllerGetImage.LogsWarningGivenImageMissingException

  • CatalogControllerGetImage.ReturnsFileResultWithBytesGivenSuccess

  • CatalogControllerGetImage.ReturnsNotFoundResultGivenImageMissingException

Eine Variante dieses Ansatzes beendet jeden Testklassennamen mit „Should“ und ändert die Zeitform:

  • CatalogControllerGetImage Sollen.RufenImageServiceWithId

  • CatalogControllerGetImage Sollen.LogWarningGivenImageMissingException

Einige Teams finden den zweiten Benennungsansatz klarer, aber etwas ausführlicher. Versuchen Sie in jedem Fall, eine Benennungskonvention zu verwenden, die Einblicke in das Testverhalten bietet. Wenn ein oder mehrere Tests fehlschlagen, ist es offensichtlich, welche Fälle fehlgeschlagen sind. Vermeiden Sie es, Ihre Tests vage zu benennen, z. B. ControllerTests.Test1, da diese Namen keinen Wert bieten, wenn sie in Testergebnissen angezeigt werden.

Wenn Sie einer Benennungskonvention wie der obigen folgen, die viele kleine Testklassen erzeugt, empfiehlt es sich, Ihre Tests mithilfe von Ordnern und Namespaces weiter zu organisieren. Abbildung 9-4 zeigt einen Ansatz zum Organisieren von Tests nach Ordner in mehreren Testprojekten.

Organisieren von Testklassen nach Ordnern basierend auf der getesteten Klasse

Abbildung 9-4. Organisieren von Testklassen nach Ordnern basierend auf der getesteten Klasse.

Wenn eine bestimmte Anwendungsklasse viele Methoden getestet hat (und somit viele Testklassen), kann es sinnvoll sein, diese Klassen in einem Ordner zu platzieren, der der Anwendungsklasse entspricht. Diese Organisation unterscheidet sich nicht davon, wie Sie Dateien an anderen Stellen in Ordnern organisieren können. Wenn Sie mehr als drei oder vier verwandte Dateien in einem Ordner haben, der viele andere Dateien enthält, ist es häufig hilfreich, sie in einen eigenen Unterordner zu verschieben.

Unit-Tests für ASP.NET Core-Apps

In einer gut gestalteten ASP.NET Core-Anwendung werden die meisten Komplexitäts- und Geschäftslogiken in Geschäftsentitäten und einer Vielzahl von Diensten gekapselt. Die ASP.NET Core MVC-App selbst mit ihren Controllern, Filtern, Viewmodels und Ansichten sollte nur wenige Komponententests erfordern. Ein Großteil der Funktionalität einer bestimmten Aktion liegt außerhalb der Aktionsmethode selbst. Tests, ob routing oder globale Fehlerbehandlung ordnungsgemäß funktionieren, können nicht effektiv mit einem Komponententest durchgeführt werden. Ebenso können Einheitstests für Filter, einschließlich Filter zur Modellüberprüfung, Authentifizierung und Autorisierung, nicht auf die Aktionsmethode eines Controllers abzielen. Ohne diese Verhaltensquellen sollten die meisten Aktionsmethoden trivial klein sein und den Großteil ihrer Arbeit an Dienste delegieren, die unabhängig vom Controller getestet werden können, der sie verwendet.

Manchmal müssen Sie Den Code umgestalten, um ihn zu testen. Häufig umfasst diese Aktivität das Identifizieren von Abstraktionen und die Verwendung der Abhängigkeitsinjektion für den Zugriff auf die Abstraktion im Code, den Sie testen möchten, anstatt direkt mit der Infrastruktur zu codieren. Betrachten Sie z. B. diese einfache Aktionsmethode zum Anzeigen von Bildern:

[HttpGet("[controller]/pic/{id}")]
public IActionResult GetImage(int id)
{
  var contentRoot = _env.ContentRootPath + "//Pics";
  var path = Path.Combine(contentRoot, id + ".png");
  Byte[] b = System.IO.File.ReadAllBytes(path);
  return File(b, "image/png");
}

Wegen der direkten Abhängigkeit von System.IO.File, die diese Methode zum Lesen aus dem Dateisystem verwendet, ist es schwer, Komponententests für sie durchzuführen. Sie können dieses Verhalten testen, um sicherzustellen, dass es erwartungsgemäß funktioniert, aber bei echten Dateien handelt es sich um einen Integrationstest. Es ist wichtig zu beachten, dass Sie den Pfad dieser Methode nicht mit einem Unit-Test prüfen können. Sie werden jedoch bald sehen, wie Sie dies mit einem Funktionstest durchführen können.

Wenn Sie das Dateisystemverhalten nicht direkt testen können und die Route nicht testen können, was bleibt dann noch zu testen? Nun, nach dem Umgestalten, um Komponententests möglich zu machen, können Sie einige Testfälle und fehlendes Verhalten entdecken, z. B. fehlerbehandlung. Was bewirkt die Methode, wenn eine Datei nicht gefunden wird? Was sollte sie tun? In diesem Beispiel sieht die umgestaltete Methode wie folgt aus:

[HttpGet("[controller]/pic/{id}")]
public IActionResult GetImage(int id)
{
  byte[] imageBytes;
  try
  {
    imageBytes = _imageService.GetImageBytesById(id);
  }
  catch (CatalogImageMissingException ex)
  {
    _logger.LogWarning($"No image found for id: {id}");
    return NotFound();
  }
  return File(imageBytes, "image/png");
}

_logger und _imageService werden beide als Abhängigkeiten eingefügt. Jetzt können Sie testen, dass dieselbe ID, die an die Aktionsmethode übergeben wird, auch an _imageService übergeben wird, und dass die resultierenden Bytes als Teil des FileResult-Objekts zurückgegeben werden. Sie können auch testen, ob die Fehlerprotokollierung erwartungsgemäß erfolgt und dass ein NotFound Ergebnis zurückgegeben wird, wenn das Bild fehlt, vorausgesetzt, dieses Verhalten ist ein wichtiges Anwendungsverhalten (d. a. nicht nur temporärer Code, den der Entwickler zum Diagnostizieren eines Problems hinzugefügt hat). Die eigentliche Dateilogik wurde in einen separaten Implementierungsdienst verschoben und wurde erweitert, um eine anwendungsspezifische Ausnahme für den Fall einer fehlenden Datei zurückzugeben. Sie können diese Implementierung unabhängig testen, indem Sie einen Integrationstest verwenden.

In den meisten Fällen sollten Sie globale Ausnahmehandler in Ihren Controllern verwenden, sodass die Menge an Logik in ihnen minimal sein sollte und wahrscheinlich kein Komponententest wert ist. Führen Sie die meisten Tests der Controlleraktionen mithilfe von Funktionstests und der TestServer unten beschriebenen Klasse durch.

Integrationstests für ASP.NET Core-Apps

Die meisten Integrationstests in Ihren ASP.NET Core-Apps sollten Dienste und andere Implementierungstypen testen, die in Ihrem Infrastrukturprojekt definiert sind. Sie könnten beispielsweise über Ihre Datenzugriffsklassen im Infrastrukturprojekt prüfen, ob EF Core die erwarteten Daten erfolgreich aktualisiert und abruft. Die beste Methode, um zu testen, dass Ihr ASP.NET Core MVC-Projekt ordnungsgemäß funktioniert, besteht darin, funktionale Tests durchzuführen, die für Ihre App ausgeführt werden, die in einem Testhost ausgeführt wird.

Funktionales Testen von ASP.NET Core-Apps

Für ASP.NET Core-Anwendungen macht die TestServer Klasse funktionale Tests relativ einfach zu schreiben. Sie konfigurieren ein TestServer mithilfe eines WebHostBuilder (oder HostBuilder) direkt (wie Sie es normalerweise für Ihre Anwendung tun) oder mit dem WebApplicationFactory-Typ (verfügbar seit Version 2.1). Versuchen Sie, Ihren Testhost so genau wie möglich an Ihren Produktionshost anzupassen, damit Ihre Tests ein Verhalten simulieren, das ähnlich dem Verhalten der App in der Produktion ist. Die WebApplicationFactory Klasse ist hilfreich zum Konfigurieren der ContentRoot des TestServers, die von ASP.NET Core verwendet wird, um statische Ressource wie Views zu finden.

Sie können einfache Funktionstests erstellen, indem Sie eine Testklasse erstellen, die IClassFixture<WebApplicationFactory<TEntryPoint>> implementiert, wobei TEntryPoint die Startup Klasse Ihrer Webanwendung darstellt. Mit dieser Schnittstelle kann Ihre Testeinrichtung mithilfe der CreateClient Methode der Fabrik einen Client erstellen.

public class BasicWebTests : IClassFixture<WebApplicationFactory<Program>>
{
  protected readonly HttpClient _client;

  public BasicWebTests(WebApplicationFactory<Program> factory)
  {
    _client = factory.CreateClient();
  }

  // write tests that use _client
}

Tipp

Wenn Sie eine minimale API-Konfiguration in Ihrer Program.cs-Datei verwenden, wird die Klasse standardmäßig intern deklariert und kann nicht über das Testprojekt darauf zugegriffen werden. Sie können stattdessen eine beliebige andere Instanzklasse in Ihrem Webprojekt auswählen oder dies zu Ihrer Program.cs Datei hinzufügen:

// Make the implicit Program class public so test projects can access it
public partial class Program { }

Häufig möchten Sie vor jedem Test eine zusätzliche Konfiguration Ihrer Website durchführen, z. B. die Anwendung für die Verwendung eines In-Memory-Datenspeichers konfigurieren und die Anwendung dann mit Seed-Daten befüllen. Um diese Funktionalität zu erreichen, erstellen Sie ihre eigene Unterklasse, WebApplicationFactory<TEntryPoint> und überschreiben Sie ihre ConfigureWebHost Methode. Das folgende Beispiel stammt aus dem eShopOnWeb FunctionalTests-Projekt und wird als Teil der Tests für die Hauptwebanwendung verwendet.

using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc.Testing;
using Microsoft.EntityFrameworkCore;
using Microsoft.eShopWeb.Infrastructure.Data;
using Microsoft.eShopWeb.Infrastructure.Identity;
using Microsoft.eShopWeb.Web;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;

namespace Microsoft.eShopWeb.FunctionalTests.Web;
public class WebTestFixture : WebApplicationFactory<Startup>
{
  protected override void ConfigureWebHost(IWebHostBuilder builder)
  {
    builder.UseEnvironment("Testing");

    builder.ConfigureServices(services =>
    {
      services.AddEntityFrameworkInMemoryDatabase();

      // Create a new service provider.
      var provider = services
            .AddEntityFrameworkInMemoryDatabase()
            .BuildServiceProvider();

      // Add a database context (ApplicationDbContext) using an in-memory
      // database for testing.
      services.AddDbContext<CatalogContext>(options =>
      {
        options.UseInMemoryDatabase("InMemoryDbForTesting");
        options.UseInternalServiceProvider(provider);
      });

      services.AddDbContext<AppIdentityDbContext>(options =>
      {
        options.UseInMemoryDatabase("Identity");
        options.UseInternalServiceProvider(provider);
      });

      // Build the service provider.
      var sp = services.BuildServiceProvider();

      // Create a scope to obtain a reference to the database
      // context (ApplicationDbContext).
      using (var scope = sp.CreateScope())
      {
        var scopedServices = scope.ServiceProvider;
        var db = scopedServices.GetRequiredService<CatalogContext>();
        var loggerFactory = scopedServices.GetRequiredService<ILoggerFactory>();

        var logger = scopedServices
            .GetRequiredService<ILogger<WebTestFixture>>();

        // Ensure the database is created.
        db.Database.EnsureCreated();

        try
        {
          // Seed the database with test data.
          CatalogContextSeed.SeedAsync(db, loggerFactory).Wait();

          // seed sample user data
          var userManager = scopedServices.GetRequiredService<UserManager<ApplicationUser>>();
          var roleManager = scopedServices.GetRequiredService<RoleManager<IdentityRole>>();
          AppIdentityDbContextSeed.SeedAsync(userManager, roleManager).Wait();
        }
        catch (Exception ex)
        {
          logger.LogError(ex, $"An error occurred seeding the " +
                    "database with test messages. Error: {ex.Message}");
        }
      }
    });
  }
}

Tests können diese benutzerdefinierte WebApplicationFactory verwenden, indem sie einen Client erstellen und dann Anforderungen an die Anwendung mithilfe dieser Clientinstanz senden. Die Anwendung verfügt über Daten, die als Teil der Assertionen des Tests verwendet werden können. Der folgende Test überprüft, ob die Startseite der eShopOnWeb-Anwendung ordnungsgemäß geladen wird und eine Produktauflistung enthält, die der Anwendung als Teil der Seed-Daten hinzugefügt wurde.

using Microsoft.eShopWeb.FunctionalTests.Web;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages;
[Collection("Sequential")]
public class HomePageOnGet : IClassFixture<WebTestFixture>
{
  public HomePageOnGet(WebTestFixture factory)
  {
    Client = factory.CreateClient();
  }

  public HttpClient Client { get; }

  [Fact]
  public async Task ReturnsHomePageWithProductListing()
  {
    // Arrange & Act
    var response = await Client.GetAsync("/");
    response.EnsureSuccessStatusCode();
    var stringResponse = await response.Content.ReadAsStringAsync();

    // Assert
    Assert.Contains(".NET Bot Black Sweatshirt", stringResponse);
  }
}

Dieser Funktionstest übt den vollständigen ASP.NET Core MVC / Razor Pages-Anwendungsstapel aus, einschließlich aller Middleware, Filter und Ordner, die vorhanden sein können. Es überprüft, ob eine bestimmte Route ("/") den erwarteten Erfolgsstatuscode und die HTML-Ausgabe zurückgibt. Dies geschieht, ohne einen echten Webserver einzurichten, und vermeidet viele der Probleme, die die Verwendung eines echten Webservers bei Tests mit sich bringen kann (z. B. Schwierigkeiten mit den Firewalleinstellungen). Funktionale Tests, die für TestServer ausgeführt werden, sind in der Regel langsamer als Integrations- und Komponententests, sind aber viel schneller als Tests, die über das Netzwerk auf einen Testwebserver ausgeführt werden. Verwenden Sie funktionale Tests, um sicherzustellen, dass der Front-End-Stapel Ihrer Anwendung wie erwartet funktioniert. Diese Tests sind besonders hilfreich, wenn Sie duplizierungen in Ihren Controllern oder Seiten finden und die Duplizierung durch Hinzufügen von Filtern beheben. Im Idealfall ändert diese Umgestaltung das Verhalten der Anwendung nicht, und eine Reihe von Funktionstests überprüft, ob dies der Fall ist.

Referenzen – Testen ASP.NET Core MVC-Apps