Testen von ASP.NET Core MVC-AppsTest ASP.NET Core MVC apps

„Wenn es Ihnen nicht gefällt, Komponententests für Ihr Produkt auszuführen, ist es sehr wahrscheinlich, dass es Ihren Kunden auch nicht gefallen wird.“"If you don't like unit testing your product, most likely your customers won't like to test it, either." _–Anonym_- Anonymous-

Software von beliebiger Komplexität kann aufgrund von Änderungen auf unerwartete Weisen fehlschlagen.Software of any complexity can fail in unexpected ways in response to changes. Daher ist es erforderlich, Anwendungen auf Änderungen zu testen, mit Ausnahme von unbedeutenden (bzw. weniger wichtigen) Anwendungen.Thus, testing after making changes is required for all but the most trivial (or least critical) applications. Manuelle Tests sind die langsamste, unzuverlässigste und aufwendigste Möglichkeit zum Testen von Software.Manual testing is the slowest, least reliable, most expensive way to test software. Leider können diese die einzige verfügbare Methode sein, wenn Anwendungen nicht zum Testen entworfen wurden.Unfortunately, if applications aren't designed to be testable, it can be the only means available. Anwendungen, die gemäß den in Kapitel 4 beschriebenen Architekturprinzipien geschrieben wurden, sollten komponententestfähig sein.Applications written to follow the architectural principles laid out in chapter 4 should be unit testable. ASP.NET Core-Anwendungen unterstützen automatisierte Integrations- und Funktionstests.ASP.NET Core applications support automated integration and functional testing.

Arten von automatisierten TestsKinds of automated tests

Es gibt viele Arten von automatisierten Tests für Softwareanwendungen.There are many kinds of automated tests for software applications. Der einfachste, spezifischste Test ist der Komponententest.The simplest, lowest level test is the unit test. Integrationstests und Funktionstests sind etwas allgemeiner.At a slightly higher level, there are integration tests and functional tests. Andere Arten von Tests werden in dieser Dokumentation nicht behandelt, z. B. UI-Tests, Auslastungstests, Belastungstests und Buildakzeptanztests.Other kinds of tests, such as UI tests, load tests, stress tests, and smoke tests, are beyond the scope of this document.

KomponententestsUnit tests

Ein Komponententest überprüft einen einzelnen Teil der Logik Ihrer Anwendung.A unit test tests a single part of your application's logic. Man kann diesen Test genauer beschreiben, indem man aufführt, was er nicht umfasst.One can further describe it by listing some of the things that it isn't. Ein Komponententest überprüft nicht, wie Ihr Code mit Abhängigkeiten oder Infrastruktur interagiert. Dafür gibt es Integrationstests.A unit test doesn't test how your code works with dependencies or infrastructure – that's what integration tests are for. Ein Komponententest überprüft nicht das Framework, auf dem Ihr Code geschrieben wurde. Sie sollten davon ausgehen, dass dieses funktioniert. Wenn es nicht funktioniert, melden Sie den Fehler und schreiben eine Problemumgehung.A unit test doesn't test the framework your code is written on – you should assume it works or, if you find it doesn't, file a bug and code a workaround. Komponententests werden vollständig im Arbeitsspeicher und im Prozess ausgeführt.A unit test runs completely in memory and in process. Sie kommunizieren nicht mit dem Dateisystem, dem Netzwerk oder einer Datenbank.It doesn't communicate with the file system, the network, or a database. Komponententests sollten Ihren Code nur überprüfen.Unit tests should only test your code.

Aufgrund der Tatsache, dass Komponententests nur eine einzelne Komponente Ihres Codes überprüfen und über keine externen Abhängigkeiten verfügen, werden diese sehr schnell ausgeführt.Unit tests, by virtue of the fact that they test only a single unit of your code, with no external dependencies, should execute extremely quickly. Daher sollten Sie eine Testsammlung aus Hunderten von Komponententests in wenigen Sekunden ausführen können.Thus, you should be able to run test suites of hundreds of unit tests in a few seconds. Führen Sie diese regelmäßig aus, im Idealfall vor jedem Push an ein öffentliches Repository für die Quellcodeverwaltung und insbesondere vor jedem automatisierten Buildvorgang auf Ihrem Buildserver.Run them frequently, ideally before every push to a shared source control repository, and certainly with every automated build on your build server.

IntegrationstestsIntegration tests

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.Although it's a good idea to encapsulate your code that interacts with infrastructure like databases and file systems, you will still have some of that code, and you will probably want to test it. 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.Additionally, you should verify that your code's layers interact as you expect when your application's dependencies are fully resolved. Hierfür sind Integrationstests verantwortlich.This is the responsibility of integration tests. Integrationstests sind langsamer und schwieriger einzurichten als Komponententests, da sie oft von externen Abhängigkeiten und Infrastrukturen abhängig sind.Integration tests tend to be slower and more difficult to set up than unit tests, because they often depend on external dependencies and infrastructure. Daher sollten Sie es vermeiden, Szenarios zu testen, die Tests mit Komponententests in Integrationstests darstellen könnten.Thus, you should avoid testing things that could be tested with unit tests in integration tests. Wenn Sie ein bestimmtes Szenario mit einem Komponententest testen können, sollten Sie dieses Szenario mit einem Komponententest testen.If you can test a given scenario with a unit test, you should test it with a unit test. Wenn dies nicht möglich ist, sollten Sie einen Integrationstest verwenden.If you can't, then consider using an integration test.

Integrationstests verfügen meistens über komplexere Setup- und Nachbereitungsprozeduren als Komponententests.Integration tests will often have more complex setup and teardown procedures than unit tests. Ein Integrationstest, der beispielsweise auf eine Datenbank angewendet wird, benötigt eine Möglichkeit, die Datenbank in einen bekannten Zustand zurückzusetzen, bevor jeder Test ausgeführt wird.For example, an integration test that goes against an actual database will need a way to return the database to a known state before each test run. Wenn neue Tests hinzugefügt werden und das Datenbankschema für die Produktion sich weiterentwickelt, werden die Testskripts größer und komplexer.As new tests are added and the production database schema evolves, these test scripts will tend to grow in size and complexity. In vielen großen Systemen ist es unpraktisch, vollständige Testsammlungen auf den Arbeitsstationen von Entwicklern auszuführen, bevor Änderungen in die Quellcodeverwaltung eingetragen werden.In many large systems, it is impractical to run full suites of integration tests on developer workstations before checking in changes to shared source control. In diesen Fällen können Integrationstests auf einem Buildserver ausgeführt werden.In these cases, integration tests may be run on a build server.

FunktionstestsFunctional tests

Integrationstests werden aus der Perspektive des Entwicklers geschrieben, um sicherzustellen, dass einige der Systemkomponenten ordnungsgemäß interagieren.Integration tests are written from the perspective of the developer, to verify that some components of the system work correctly together. Funktionstests werden aus der Perspektive des Benutzers geschrieben, um die Richtigkeit des Systems basierend auf den Anforderungen sicherzustellen.Functional tests are written from the perspective of the user, and verify the correctness of the system based on its requirements. Der folgende Auszug bietet eine nützliche Analogie zum Vergleich von Funktionstests und Komponententests:The following excerpt offers a useful analogy for how to think about functional tests, compared to unit tests:

„Oft wird das Entwickeln eines Systems mit dem Erbauen eines Hauses verglichen."Many times the development of a system is likened to the building of a house. Auch wenn diese Analogie nicht ganz richtig ist, können wir sie verwenden, um den Unterschied zwischen Komponententests und Funktionstests besser zu verstehen.While this analogy isn't quite correct, we can extend it for the purposes of understanding the difference between unit and functional tests. Komponententests entsprechen einem Bauinspektor, der die Baustelle eines Hauses begutachtet.Unit testing is analogous to a building inspector visiting a house's construction site. Er konzentriert sich auf die verschiedenen internen Systeme des Hauses, also das Gebäudefundament, den Rohbau, die Stromversorgung, die sanitären Einrichtungen, usw.He is focused on the various internal systems of the house, the foundation, framing, electrical, plumbing, and so on. Er stellt sicher (überprüft), dass die einzelnen Teile des Hauses voll funktionsfähig und sicher sind, d.h., dass sie der Bauordnung entsprechen.He ensures (tests) that the parts of the house will work correctly and safely, that is, meet the building code. Funktionstests entsprechen in diesem Szenario dem Hauseigentümer, der ebenfalls diese Baustelle besucht.Functional tests in this scenario are analogous to the homeowner visiting this same construction site. Er geht davon aus, dass die internen Systeme funktionieren und der Bauinspektor seine Arbeit macht.He assumes that the internal systems will behave appropriately, that the building inspector is performing his task. Der Hauseigentümer konzentriert sich darauf, wie es sein wird, in diesem Haus zu leben.The homeowner is focused on what it will be like to live in this house. Er kümmert sich zum Beispiel darum, wie das Haus aussieht, ob die verschiedenen Räume die passende Größe haben, ob das Haus den Bedürfnissen einer Familie gerecht wird, und ob die Fenster eine gute Ausrichtung dafür haben, die Morgensonne herein zu lassen.He is concerned with how the house looks, are the various rooms a comfortable size, does the house fit the family's needs, are the windows in a good spot to catch the morning sun. Der Hauseigentümer führt Funktionstests am Haus durch.The homeowner is performing functional tests on the house. Seine Perspektive ist die des Benutzers.He has the user's perspective. Der Bauinspektor führt Komponententests am Haus durch.The building inspector is performing unit tests on the house. Seine Perspektive ist die des Entwicklers.“He has the builder's perspective."

Quelle: Unit Testing versus Functional Tests (Vergleich von Komponententests und Funktionstests)Source: Unit Testing versus Functional Tests

Unter Entwicklern sagt man gern: „Als Entwickler machen wir zwei Fehler: wir erstellen falsch, oder wir erstellen das Falsche.“I'm fond of saying "As developers, we fail in two ways: we build the thing wrong, or we build the wrong thing." Mit Komponententests stellen Sie sicher, dass Sie richtig erstellen. Funktionstests stellen sicher, dass Sie das Richtige erstellen.Unit tests ensure you are building the thing right; functional tests ensure you are building the right thing.

Da Funktionstest auf der Systemebene ausgeführt werden, erfordern sie ein gewisses Maß an Automatisierung der Benutzeroberfläche.Since functional tests operate at the system level, they may require some degree of UI automation. Wie Integrationstests arbeiten auch die Funktionstests in der Regel mit einer Art Testinfrastruktur.Like integration tests, they usually work with some kind of test infrastructure as well. Dadurch werden sie langsamer und anfälliger für Fehler als Komponententests und Integrationstests.This makes them slower and more brittle than unit and integration tests. Sie sollten nur so viele Funktionstests anwenden, wie Sie benötigen, um sich sicher zu sein, dass das System sich so verhält, wie Benutzer es erwarten würden.You should have only as many functional tests as you need to be confident the system is behaving as users expect.

TestpyramideTesting Pyramid

Martin Fowler schrieb über die Testpyramide. Ein Beispiel dafür wird in Abbildung 9–1 dargestellt.Martin Fowler wrote about the testing pyramid, an example of which is shown in Figure 9-1.

Testpyramide

Abbildung 9-1.Figure 9-1. TestpyramideTesting Pyramid

Die verschiedenen Schichten der Pyramide und ihre Größen stellen verschiedene Arten von Tests dar, und wie viele davon Sie für Ihre Anwendung schreiben sollten.The different layers of the pyramid, and their relative sizes, represent different kinds of tests and how many you should write for your application. Wie Sie sehen können, wird empfohlen, eine große Basis aus Komponententests zu verwenden, die von einer kleineren Menge von Integrationstests und einer noch kleineren Menge von Funktionstests unterstützt wird.As you can see, the recommendation is to have a large base of unit tests, supported by a smaller layer of integration tests, with an even smaller layer of functional tests. Im Idealfall sollte jede Schicht nur aus Tests bestehen, die auf niedrigeren Schichten nicht ordnungsgemäß ausgeführt werden können.Each layer should ideally only have tests in it that cannot be performed adequately at a lower layer. Behalten Sie die Testpyramide im Hinterkopf, wenn Sie sich entscheiden, welche Art von Test Sie für ein bestimmtes Szenario benötigen.Keep the testing pyramid in mind when you are trying to decide which kind of test you need for a particular scenario.

Der TestgegenstandWhat to test

Herauszufinden, was getestet werden sollte, ist ein häufiges Problem für Entwickler, die noch unerfahren im Schreiben von automatisierten Tests sind.A common problem for developers who are inexperienced with writing automated tests is coming up with what to test. Ein guter Startpunkt ist das Testen der bedingten Logik.A good starting point is to test conditional logic. An jeder Stelle, an der eine Methode mit einem Verhalten vorhanden ist, das auf einer Bedingungsanweisung basiert (if-else, switch usw.), sollten Sie zumindest einige Tests erstellen, die das richtige Verhalten unter bestimmten Bedingungen überprüfen.Anywhere you have a method with behavior that changes based on a conditional statement (if-else, switch, and so on), you should be able to come up with at least a couple of tests that confirm the correct behavior for certain conditions. Wenn Ihr Code über Fehlerbedingungen verfügt, sollten Sie mindestens einen Test für den „besten Pfad“ durch den Code (ohne Fehler) und einen Test für den „schlechtesten Pfad“ (mit Fehlern und ungewohnten Ergebnissen) verwenden, um sicherzustellen, dass Ihre Anwendung auf Fehler wie erwartet reagiert.If your code has error conditions, it's good to write at least one test for the "happy path" through the code (with no errors), and at least one test for the "sad path" (with errors or atypical results) to confirm your application behaves as expected in the face of errors. Letztendlich sollten Sie sich darauf konzentrieren, Dinge zu testen, die fehlschlagen können, anstatt sich auf Metriken wie Code Coverage zu konzentrieren.Finally, try to focus on testing things that can fail, rather than focusing on metrics like code coverage. Allgemein ist mehr Code Coverage besser als zu wenig.More code coverage is better than less, generally. Allerdings können Sie Ihre Zeit sinnvoller nutzen, wenn Sie ein paar weitere Tests für eine komplexe und unternehmenskritische Methode schreiben, anstatt Tests für automatische Eigenschaften zu schreiben, um die Code Coverage-Metrik zu verbessern.However, writing a few more tests of a complex and business-critical method is usually a better use of time than writing tests for auto-properties just to improve test code coverage metrics.

Organisieren von TestprojektenOrganizing test projects

Sie können Testprojekte so organisieren, wie es für Sie am besten funktioniert.Test projects can be organized however works best for you. Es kann hilfreich sein, Tests je nach Typ (Komponententest, Integrationstest) und Testsubjekt (Projekt, Namespace) voneinander zu trennen.It's a good idea to separate tests by type (unit test, integration test) and by what they are testing (by project, by namespace). Ob diese Trennung aus Ordnern in einem einzelnen Testprojekt oder mehreren Testprojekten besteht, ist eine Entwurfsentscheidung.Whether this separation consists of folders within a single test project, or multiple test projects, is a design decision. Ein einzelnes Projekt ist am einfachsten. Für ein großes Projekt mit vielen Tests sollten Sie mehrere verschiedene Testprojekte besitzen, um verschiedene Testgruppen einfacher ausführen zu können.One project is simplest, but for large projects with many tests, or in order to more easily run different sets of tests, you might want to have several different test projects. Viele Teams organisieren Ihre Testprojekte basierend auf dem Projekt, das sie überprüfen. Dies resultiert bei Anwendungen mit einer größeren Anzahl von Projekten in einer großen Anzahl von Testprojekten, insbesondere dann, wenn Sie diese immer noch nach der Art von Tests in jedem Projekt sortieren.Many teams organize test projects based on the project they are testing, which for applications with more than a few projects can result in a large number of test projects, especially if you still break these down according to what kind of tests are in each project. Ein Kompromiss für diesen Ansatz ist, ein Projekt mit Ordnern in den Testprojekten pro Art von Test für jede Anwendung zu besitzen, die das Projekt und die Klasse angeben, die geprüft werden.A compromise approach is to have one project per kind of test, per application, with folders inside the test projects to indicate the project (and class) being tested.

Eine gängige Methode ist, die Anwendungsprojekte in einem Ordner „src“ und die Testprojekte der Anwendung in einem parallelen Ordner „tests“ zu organisieren.A common approach is to organize the application projects under a 'src' folder, and the application's test projects under a parallel 'tests' folder. Sie können entsprechende Projektmappenordner in Visual Studio anlegen, wenn Sie diese Organisierung hilfreich finden.You can create matching solution folders in Visual Studio, if you find this organization useful.

Organisieren von Tests in Ihrer Projektmappe

Abbildung 9-2:Figure 9-2. Organisieren von Tests in Ihrer ProjektmappeTest organization in your solution

Sie können das Testframework verwenden, das Sie bevorzugen.You can use whichever test framework you prefer. Das xUnit-Framework funktioniert gut und wird für alle Tests für ASP.NET Core und EF Core verwendet.The xUnit framework works well and is what all of the ASP.NET Core and EF Core tests are written in. Mit der in Abbildung 9.3 gezeigten Vorlage können Sie ein xUnit-Testprojekt in Visual Studio oder über die CLI mithilfe des Befehls dotnet new xunit hinzufügen.You can add an xUnit test project in Visual Studio using the template shown in Figure 9-3, or from the CLI using dotnet new xunit.

Hinzufügen eines xUnit-Testprojekts in Visual Studio

Abbildung 9-3.Figure 9-3. Hinzufügen eines xUnit-Testprojekts in Visual StudioAdd an xUnit Test Project in Visual Studio

Benennen von TestsTest naming

Weisen Sie Ihren Tests konsistente Namen zu, die den Zweck des jeweiligen Test angeben.Name your tests in a consistent fashion, with names that indicate what each test does. Ein effektiver Ansatz ist, Testklassen nach der Klasse und Methode zu benennen, die sie testen.One approach I've had great success with is to name test classes according to the class and method they are testing. Dies resultiert in vielen kleinen Testklassen, verdeutlicht jedoch, wofür jeder Test zuständig ist.This results in many small test classes, but it makes it extremely clear what each test is responsible for. Mit dem eingerichteten Testklassennamen zum Identifizieren der Klasse und Methode, die getestet werden, kann der Testmethodenname dafür verwendet werden, das zu testende Verhalten anzugeben.With the test class name set up to identify the class and method to be tested, the test method name can be used to specify the behavior being tested. Dies sollte das erwartete Verhalten und alle Eingaben oder Annahmen einschließen, die dieses Verhalten verursachen.This should include the expected behavior and any inputs or assumptions that should yield this behavior. Beispiele für Testnamen:Some example test names:

  • CatalogControllerGetImage.CallsImageServiceWithId

  • CatalogControllerGetImage.LogsWarningGivenImageMissingException

  • CatalogControllerGetImage.ReturnsFileResultWithBytesGivenSuccess

  • CatalogControllerGetImage.ReturnsNotFoundResultGivenImageMissingException

Eine Variante dieses Ansatzes beendet jeden Testklassennamen mit „Should“ und ändert die Zeitform:A variation of this approach ends each test class name with "Should" and modifies the tense slightly:

  • CatalogControllerGetImageShould.CallImageServiceWithIdCatalogControllerGetImageShould.CallImageServiceWithId

  • CatalogControllerGetImageShould.LogWarningGivenImageMissingExceptionCatalogControllerGetImageShould.LogWarningGivenImageMissingException

Einige Teams finden den zweiten Ansatz für die Benennung klarer, obwohl er etwas ausführlicher ist.Some teams find the second naming approach clearer, though slightly more verbose. In jedem Fall sollten Sie versuchen, eine Namenskonvention zu verwenden, die einen Einblick zum Verhalten des Tests bietet, damit klar ist, welche Fälle fehlschlagen.In any case, try to use a naming convention that provides insight into test behavior, so that when one or more tests fail, it's obvious from their names what cases have failed. Vermeiden Sie ungenaue Namen, z. B. „ControllerTests.Test1“, da diese keine Informationen bieten, wenn sie in den Testergebnissen angezeigt werden.Avoid naming your tests vaguely, such as ControllerTests.Test1, as these offer no value when you see them in test results.

Wenn Sie eine Namenskonvention befolgen, die viele kleine Testklassen produziert, wie eine der oben genannten, empfiehlt es sich, Ihre Tests auch mit Ordnern und Namespaces zu sortieren.If you follow a naming convention like the one above that produces many small test classes, it's a good idea to further organize your tests using folders and namespaces. Abbildung 9–4 veranschaulicht einen Ansatz zum Organisieren von Tests mit Ordnern in mehreren Testprojekten.Figure 9-4 shows one approach to organizing tests by folder within several test projects.

Organisieren von Testklassen in Ordnern, basierend auf der getesteten Klasse

Abbildung 9–4Figure 9-4. Organisieren von Testklassen in Ordnern, basierend auf der Klasse, die getestet wird.Organizing test classes by folder based on class being tested.

Wenn viele Methoden (d. h. auch viele Testklassen) in einer Anwendungsklasse getestet werden sollen, kann es sinnvoll sein, diese in einem Ordner abzulegen, der der Anwendungsklasse entspricht.If a particular application class has many methods being tested (and thus many test classes), it may make sense to place these in a folder corresponding to the application class. Diese Organisierung gleicht der Organisierung von Dateien in Ordnern.This organization is no different than how you might organize files into folders elsewhere. Wenn mehr als drei oder vier zusammengehörende Dateien in einem Ordner mit vielen anderen Dateien vorhanden sind, empfiehlt es sich, für diese einen Unterordner anzulegen.If you have more than three or four related files in a folder containing many other files, it's often helpful to move them into their own subfolder.

Komponententests für ASP.NET Core-AppsUnit testing ASP.NET Core apps

In einer gut entworfenen ASP.NET Core-Anwendung ist der Großteil der Komplexitäts- und Geschäftslogik in Geschäftselementen und einer Vielzahl von Diensten gekapselt.In a well-designed ASP.NET Core application, most of the complexity and business logic will be encapsulated in business entities and a variety of services. Die ASP.NET Core MVC-App selbst sollte mitsamt ihrer Controller, Filter, ViewModels und Ansichten nur wenige Komponententests benötigen.The ASP.NET Core MVC app itself, with its controllers, filters, viewmodels, and views, should require very few unit tests. Viele der Funktionen einer bestimmten Aktion befinden sich außerhalb der Aktionsmethode.Much of the functionality of a given action lies outside the action method itself. Ob das Routing oder die globale Fehlerbehandlung ordnungsgemäß funktioniert, kann mit einem Komponententest nicht effektiv geprüft werden.Testing whether routing works correctly, or global error handling, cannot be done effectively with a unit test. Ebenso können alle Filter, einschließlich der Modellvalidierung und die Authentifizierung sowie die Autorisierungsfilter nicht mit Komponententests geprüft werden, die auf die Aktionsmethode eines Controllers ausgerichtet sind.Likewise, any filters, including model validation and authentication and authorization filters, cannot be unit tested with a test targeting a controller's action method. Ohne diese Quellen für Verhalten sollten die meisten Aktionsmethoden unbedeutend klein sein und das meiste Ihrer Arbeit an Dienste delegieren, die unabhängig vom Controller geprüft werden, der sie verwendet.Without these sources of behavior, most action methods should be trivially small, delegating the bulk of their work to services that can be tested independent of the controller that uses them.

In manchen Fällen müssen Sie Ihren Code umgestalten, um einen Komponententest durchführen zu können.Sometimes you'll need to refactor your code in order to unit test it. Dies umfasst häufig das Identifizieren von Abstraktionen und die Verwendung von Abhängigkeitsinjektionen, um auf den Code zuzugreifen, den Sie testen möchten, anstatt direkt auf die Infrastruktur zu codieren.Frequently this involves identifying abstractions and using dependency injection to access the abstraction in the code you'd like to test, rather than coding directly against infrastructure. Ziehen Sie zum Beispiel die folgende einfache Aktionsmethode zum Anzeigen von Bildern in Betracht:For example, consider this simple action method for displaying images:

[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.Unit testing this method is made difficult by its direct dependency on System.IO.File, which it uses to read from the file system. Sie können dieses Verhalten testen, um sicherzustellen, dass es wie erwartet funktioniert. Wenn Sie dies jedoch mit echten Dateien durchführen, handelt es sich um einen Integrationstest.You can test this behavior to ensure it works as expected, but doing so with real files is an integration test. Beachten Sie, dass Sie die Route dieser Methode nicht mit Komponententests testen können. Wie Sie diese mit einem Funktionstest testen können, wird im Folgenden erläutert.It's worth noting you can't unit test this method's route – you'll see how to do this with a functional test shortly.

Wenn Sie keinen direkten Komponententest für das Verhalten des Dateisystems und die Route durchführen können, gibt es dennoch Tests, die Sie durchführen sollten.If you can't unit test the file system behavior directly, and you can't test the route, what is there to test? Nach dem Refactoring zum Ermöglichen von Komponententests werden Ihnen möglicherweise Testfälle und fehlendes Verhalten auffallen, wie z.B. die Problembehandlung.Well, after refactoring to make unit testing possible, you may discover some test cases and missing behavior, such as error handling. Wie reagiert die Methode, wenn eine Datei nicht gefunden werden kann?What does the method do when a file isn't found? Wie sollte sie reagieren?What should it do? In diesem Beispiel sieht die umgestaltete Methode wie folgt aus:In this example, the refactored method looks like this:

[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 als Abhängigkeiten eingefügt._logger and _imageService are both injected as dependencies. Sie können nun prüfen, ob dieselbe ID, die an die Aktionsmethode übergeben wird, an _imageService übergeben wird und ob die resultierenden Bytes als Teil von FileResult zurückgegeben werden.Now you can test that the same ID that is passed to the action method is passed to _imageService, and that the resulting bytes are returned as part of the FileResult. Sie können auch überprüfen, ob die Fehlerprotokollierung ordnungsgemäß erfolgt, und ob das Ergebnis NotFound zurückgegeben wird, wenn das Bild fehlt, vorausgesetzt, dass dies wichtig für das Verhalten der Anwendung ist (d. h., dass dies nicht nur temporärer Code ist, der vom Entwickler hinzugefügt wurde, um ein Problem zu diagnostizieren).You can also test that error logging is happening as expected, and that a NotFound result is returned if the image is missing, assuming this is important application behavior (that is, not just temporary code the developer added to diagnose an issue). Die eigentliche Dateilogik wurde in einen separaten Implementierungsdienst verschoben und wurde erweitert, damit sie im Fall einer fehlenden Datei eine anwendungsspezifische Ausnahme zurückgibt.The actual file logic has moved into a separate implementation service, and has been augmented to return an application-specific exception for the case of a missing file. Mit einem Integrationstest können Sie diese Implementierung unabhängig testen.You can test this implementation independently, using an integration test.

Für die meisten Fälle wird empfohlen, globale Ausnahmehandler in Ihren Controllern zu verwenden. Darum sollten der enthaltene Logikumfang minimal und Komponententests wahrscheinlich nicht notwendig sein.In most cases, you'll want to use global exception handlers in your controllers, so the amount of logic in them should be minimal and probably not worth unit testing. Verwenden Sie für Tests von Controlleraktionen nach Möglichkeit Funktionstests und die unten beschriebene TestServer-Klasse.Do most of your testing of controller actions using functional tests and the TestServer class described below.

Integrationstests für ASP.NET Core-AppsIntegration testing ASP.NET Core apps

Die meisten Integrationstests in Ihren ASP.NET Core-Apps sollten Testdienste und andere Implementierungstypen sein, die in Ihrem Infrastrukturprojekt definiert wurden.Most of the integration tests in your ASP.NET Core apps should be testing services and other implementation types defined in your Infrastructure project. Sie könnten beispielsweise über Ihre Datenzugriffsklassen im Infrastrukturprojekt prüfen, ob EF Core die erwarteten Daten erfolgreich aktualisiert und abruft.For example, you could test that EF Core was successfully updating and retrieving the data that you expect from your data access classes residing in the Infrastructure project. Das korrekte Verhalten Ihres MVC-Projekts in ASP.NET Core können Sie am besten mit Funktionstests testen, die Sie für Ihre App ausführen, welche in einem Testhost ausgeführt wird.The best way to test that your ASP.NET Core MVC project is behaving correctly is with functional tests that run against your app running in a test host.

Funktionstests für ASP.NET Core-AppsFunctional testing ASP.NET Core apps

Die TestServer-Klasse macht das Schreiben von Funktionstests für ASP.NET Core-Anwendungen relativ einfach.For ASP.NET Core applications, the TestServer class makes functional tests fairly easy to write. Sie konfigurieren einen TestServer mit WebHostBuilder (oder HostBuilder) direkt (wie Sie es normalerweise für Ihre Anwendung tun) oder mit dem Typ WebApplicationFactory (verfügbar seit Version 2.1).You configure a TestServer using a WebHostBuilder (or HostBuilder) directly (as you normally do for your application), or with the WebApplicationFactory type (available since version 2.1). Verwenden Sie nach Möglichkeit einen Testhost, der dem Produktionshost so ähnlich wie möglich ist, damit das Verhalten der Tests dem Verhalten der App in der Produktion ähnelt.Try to match your test host to your production host as closely as possible, so your tests exercise behavior similar to what the app will do in production. Die WebApplicationFactory-Klasse ist hilfreich für die ContentRoot-Konfiguration der TestServer-Klasse, die von ASP.NET Core verwendet wird, um statische Ressourcen wie Ansichten zu finden.The WebApplicationFactory class is helpful for configuring the TestServer's ContentRoot, which is used by ASP.NET Core to locate static resource like Views.

Sie können einfache Funktionstests erstellen, indem Sie eine Testklasse erstellen, die IClassFixture<WebApplicationFactory<TEntry>> implementiert, wobei es sich bei „TEntry“ um die Startklasse Ihrer Webanwendung handelt.You can create simple functional tests by creating a test class that implements IClassFixture<WebApplicationFactory<TEntry>> where TEntry is your web application's Startup class. Mit diesen Vorkehrungen kann Ihre Testfixture einen Client mithilfe der CreateClient-Methode der Zuordnungsinstanz erstellen:With this in place, your test fixture can create a client using the factory's CreateClient method:

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

    public BaseWebTest(WebApplicationFactory<Startup> factory)
    {
        _client = factory.CreateClient();
    }

    // write tests that use _client
}

In vielen Fällen führen Sie zusätzliche Konfigurationen für Ihre Website durch, bevor jeder Test ausgeführt wird, z.B. das Konfigurieren der Anwendung für die Verwendung eines Datenspeichers im Arbeitsspeicher und das anschließende Seeding der Anwendung mit Testdaten.Frequently, you'll want to perform some additional configuration of your site before each test runs, such as configuring the application to use an in memory data store and then seeding the application with test data. Hierzu sollten Sie eine eigene Unterklasse von „WebApplicationFactory<TEntry>“ erstellen und die dazugehörige Methode „ConfigureWebHost“ überschreiben.To do this, you should create your own subclass of WebApplicationFactory<TEntry> and override its ConfigureWebHost method. Das folgende Beispiel stammt vom Funktionstestprojekt „eShopOnWeb“ und wird als Teil der Tests für die Hauptwebanwendung verwendet.The example below is from the eShopOnWeb FunctionalTests project and is used as part of the tests on the main web application.

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 die benutzerdefinierte WebApplicationFactory nutzen, um einen Client zu erstellen und dann mithilfe dieser Clientinstanz Anforderungen an die Anwendung zu stellen.Tests can make use of this custom WebApplicationFactory by using it to create a client and then making requests to the application using this client instance. Die Anwendung verfügt über Daten, die als Teil der Assertionen des Tests verwendet werden können.The application will have data seeded that can be used as part of the test's assertions. Der folgende Test überprüft, ob die Startseite der Anwendung „eShopOnWeb“ ordnungsgemäß geladen wird und eine Produktliste enthält, die der Anwendung als Teil des Seedings hinzugefügt wurden.The following test verifies that the home page of the eShopOnWeb application loads correctly and includes a product listing that was added to the application as part of the seed data.

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 führt den gesamten ASP.NET Core MVC/Razor Pages-Anwendungsstapel aus, einschließlich aller Middleware, Filter, Binder usw., die verfügbar sind.This functional test exercises the full ASP.NET Core MVC / Razor Pages application stack, including all middleware, filters, binders, etc. that may be in place. Er überprüft, ob eine bestimmte Route („/“) den erwarteten Erfolgsstatuscode und die HTML-Ausgabe zurückgibt.It verifies that a given route ("/") returns the expected success status code and HTML output. Das funktioniert, ohne dass ein echter Webserver eingerichtet werden muss, und vermeidet deshalb einen Großteil der Fehleranfälligkeit, die bei einem echten Webserver auftreten kann (z.B. Probleme mit den Einstellungen der Firewall).It does so without setting up a real web server, and so avoids much of the brittleness that using a real web server for testing can experience (for example, problems with firewall settings). Funktionstests, die für den TestServer ausgeführt werden, sind in der Regel langsamer als Integrations- und Komponententests, aber deutlich schneller als Tests, die über das Netzwerk auf einem Testwebserver ausgeführt werden.Functional tests that run against TestServer are usually slower than integration and unit tests, but are much faster than tests that would run over the network to a test web server. Verwenden Sie Funktionstests, um sicherzustellen, dass der Front-End-Stapel Ihrer Anwendung wie erwartet funktioniert.Use functional tests to ensure your application's front-end stack is working as expected. Diese Tests sind besonders hilfreich, wenn Sie Duplizierung in Ihren Controllern oder Seiten finden und Sie diese durch Hinzufügen von Filtern behandeln.These tests are especially useful when you find duplication in your controllers or pages and you address the duplication by adding filters. Im Idealfall ändert dieses Refactoring nicht das Verhalten der Anwendung, und eine Reihe von Funktionstests überprüft, ob dies der Fall ist.Ideally, this refactoring won't change the behavior of the application, and a suite of functional tests will verify this is the case.

Ressourcen: Testen von ASP.NET Core MVC-AppsReferences – Test ASP.NET Core MVC apps