Testowanie aplikacji ASP.NET Core MVCTest ASP.NET Core MVC apps

"Jeśli nie podoba Ci się testowanie jednostkowe produktu, najprawdopodobniej nie chcesz przetestować go.""If you don't like unit testing your product, most likely your customers won't like to test it, either." _Anonimowe_- Anonymous-

Oprogramowanie dowolnej złożoności może zakończyć się niepowodzeniem na nieoczekiwanych sposobach w reakcji na zmiany.Software of any complexity can fail in unexpected ways in response to changes. W ten sposób testowanie po wprowadzeniu zmian jest wymagane dla wszystkich, ale najbardziej prostych (lub najmniej krytycznych) aplikacji.Thus, testing after making changes is required for all but the most trivial (or least critical) applications. Testowanie ręczne to najwolniejsze, najmniej niezawodne, najbardziej kosztowne rozwiązanie do testowania oprogramowania.Manual testing is the slowest, least reliable, most expensive way to test software. Niestety, jeśli aplikacje nie mają być weryfikowalne, może to być tylko dostępne.Unfortunately, if applications aren't designed to be testable, it can be the only means available. Aplikacje przygotowane do przestrzegania zasad architektonicznych, które zostały określone w rozdziale 4 powinny być weryfikowalne jednostkowym.Applications written to follow the architectural principles laid out in chapter 4 should be unit testable. Aplikacje ASP.NET Core obsługują zautomatyzowaną integrację i testowanie funkcjonalne.ASP.NET Core applications support automated integration and functional testing.

Rodzaje testów automatycznychKinds of automated tests

Istnieje wiele rodzajów zautomatyzowanych testów dla aplikacji oprogramowania.There are many kinds of automated tests for software applications. Najprostszym, najniższym poziomem testu jest test jednostkowy.The simplest, lowest level test is the unit test. Na nieco wyższym poziomie istnieją testy integracji i testy funkcjonalne.At a slightly higher level, there are integration tests and functional tests. Inne rodzaje testów, takie jak testy interfejsu użytkownika, testy obciążeniowe, testy obciążeniowe i testy dymu, wykraczają poza zakres tego dokumentu.Other kinds of tests, such as UI tests, load tests, stress tests, and smoke tests, are beyond the scope of this document.

Testy jednostkoweUnit tests

Test jednostkowy testów pojedynczej części logiki aplikacji.A unit test tests a single part of your application's logic. Jeden z nich może jeszcze bardziej opisać, wyświetlając niektóre z tych rzeczy.One can further describe it by listing some of the things that it isn't. Test jednostkowy nie testuje, w jaki sposób kod współpracuje z zależnościami lub infrastrukturą — to są testy integracji.A unit test doesn't test how your code works with dependencies or infrastructure – that's what integration tests are for. Test jednostkowy nie przetestuje struktury kodu, w którym jest on pisany — należy zastanowić się, że działa lub, jeśli okaże się, że nie, należy zgłosić błąd i napisać obejście problemu.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. Test jednostkowy jest całkowicie wykonywany w pamięci i w procesie.A unit test runs completely in memory and in process. Nie komunikuje się z systemem plików, siecią lub bazą danych.It doesn't communicate with the file system, the network, or a database. Testy jednostkowe powinny tylko testować kod.Unit tests should only test your code.

Testy jednostkowe, na mocy których testuje tylko jedną jednostkę kodu bez zależności zewnętrznych, powinny być wykonywane bardzo szybko.Unit tests, by virtue of the fact that they test only a single unit of your code, with no external dependencies, should execute extremely fast. Z tego względu powinno być możliwe uruchamianie zestawów testów dla setek testów jednostkowych w ciągu kilku sekund.Thus, you should be able to run test suites of hundreds of unit tests in a few seconds. Uruchamiaj je często, najlepiej przed każdym wypchnięciem do udostępnionego repozytorium kontroli źródła i z pewnością przy każdej zautomatyzowanej kompilacji na serwerze kompilacji.Run them frequently, ideally before every push to a shared source control repository, and certainly with every automated build on your build server.

Testy integracjiIntegration tests

Chociaż dobrym pomysłem jest Hermetyzowanie kodu, który współdziała z infrastrukturą, taką jak bazy danych i systemy plików, nadal będziesz mieć część tego kodu i prawdopodobnie chcesz go przetestować.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. Ponadto należy sprawdzić, czy warstwy kodu działają w oczekiwany sposób, gdy zależności aplikacji są w pełni rozwiązane.Additionally, you should verify that your code's layers interact as you expect when your application's dependencies are fully resolved. Ta funkcja jest odpowiedzialna za testy integracji.This functionality is the responsibility of integration tests. Testy integracji są znacznie wolniejsze i trudniejsze do skonfigurowania niż testy jednostkowe, ponieważ często zależą od zewnętrznych zależności i infrastruktury.Integration tests tend to be slower and more difficult to set up than unit tests, because they often depend on external dependencies and infrastructure. W tym celu należy unikać testowania, które mogą być testowane przy użyciu testów jednostkowych w testach integracji.Thus, you should avoid testing things that could be tested with unit tests in integration tests. Jeśli można testować dany scenariusz z testem jednostkowym, należy przetestować go z testem jednostkowym.If you can test a given scenario with a unit test, you should test it with a unit test. Jeśli nie jest to możliwe, rozważ użycie testu integracji.If you can't, then consider using an integration test.

Testy integracji często mają bardziej skomplikowane ustawienia i usuwania procedury niż testy jednostkowe.Integration tests will often have more complex setup and teardown procedures than unit tests. Na przykład test integracji, który przechodzi względem rzeczywistej bazy danych, będzie wymagał metody przywrócenia bazy danych do znanego stanu przed każdym uruchomieniem testu.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. Po dodaniu nowych testów, a produkcyjny schemat bazy danych, te skrypty te zależą od rozmiaru i złożoności.As new tests are added and the production database schema evolves, these test scripts will tend to grow in size and complexity. W wielu dużych systemach nie ma praktycznego uruchamiania pełnych zestawów testów integracji na stacjach roboczych deweloperów przed zaewidencjonowaniem zmian w udostępnionej kontroli źródła.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. W takich przypadkach testy integracji mogą być uruchamiane na serwerze kompilacji.In these cases, integration tests may be run on a build server.

Testy funkcjonalneFunctional tests

Testy integracji są napisywane od perspektywy dewelopera, aby sprawdzić, czy niektóre składniki systemu działają prawidłowo.Integration tests are written from the perspective of the developer, to verify that some components of the system work correctly together. Testy funkcjonalne są zapisywane z perspektywy użytkownika i sprawdzają poprawność systemu w zależności od wymagań.Functional tests are written from the perspective of the user, and verify the correctness of the system based on its requirements. Poniższy fragment przedstawia przydatną funkcję analogową do rozważania, jak należy wziąć pod uwagę testy funkcjonalne w porównaniu z testami jednostkowymi:The following excerpt offers a useful analogy for how to think about functional tests, compared to unit tests:

"Wiele razy rozwój systemu jest likened do budynku w domu."Many times the development of a system is likened to the building of a house. Chociaż ta wartość analogiczna nie jest poprawna, możemy ją rozłożyć na potrzeby ustalenia różnicy między testami jednostkowymi i funkcjonalnymi.While this analogy isn't quite correct, we can extend it for the purposes of understanding the difference between unit and functional tests. Testy jednostkowe są analogiczne do Inspektora konstrukcyjnego odwiedzającego witrynę konstrukcyjną domu.Unit testing is analogous to a building inspector visiting a house's construction site. Koncentruje się na różnych systemach wewnętrznych, fundamentach, ramkach, elektrycznych, wodociągowych i tak dalej.He is focused on the various internal systems of the house, the foundation, framing, electrical, plumbing, and so on. Gwarantuje (testy), że części domu będą działały prawidłowo i bezpiecznie, czyli spełniają kod budynku.He ensures (tests) that the parts of the house will work correctly and safely, that is, meet the building code. Testy funkcjonalne w tym scenariuszu są analogiczne do Homeowner odwiedzania tej samej lokacji konstrukcja.Functional tests in this scenario are analogous to the homeowner visiting this same construction site. Przyjęto założenie, że systemy wewnętrzne będą działać odpowiednio, że Inspektor budynku wykonuje jego zadanie.He assumes that the internal systems will behave appropriately, that the building inspector is performing his task. Homeowner koncentruje się na tym, co będzie wyglądać na żywo w tym domu.The homeowner is focused on what it will be like to live in this house. Ma ona wpływ na wygląd domu, czy różne pokoje są wygodne, czy najlepiej odpowiadają potrzebom rodziny, czy w systemie Windows jest dobrym miejscem, aby przechwycić rano Sun.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. Homeowner wykonuje testy funkcjonalne w domu.The homeowner is performing functional tests on the house. Ma perspektywę użytkownika.He has the user's perspective. Inspektor budowlany przeprowadza testy jednostkowe w domu.The building inspector is performing unit tests on the house. Ma perspektywę konstruktora ".He has the builder's perspective."

Źródło: testowanie jednostkowe i testy funkcjonalneSource: Unit Testing versus Functional Tests

Fond się powiedzieć "jako Deweloperzy, ale kończymy się niepowodzeniem na dwa sposoby: możemy utworzyć niewłaściwy element lub stworzyć niewłaściwy element".I'm fond of saying "As developers, we fail in two ways: we build the thing wrong, or we build the wrong thing." Testy jednostkowe gwarantują, że tworzysz to prawo; testy funkcjonalne zapewniają, że tworzysz odpowiednie rzeczy.Unit tests ensure you are building the thing right; functional tests ensure you are building the right thing.

Ponieważ testy funkcjonalne działają na poziomie systemu, mogą wymagać pewnego stopnia automatyzacji interfejsu użytkownika.Since functional tests operate at the system level, they may require some degree of UI automation. Podobnie jak w przypadku testów integracji, zazwyczaj działają one również w przypadku niektórych rodzajów infrastruktury testowej.Like integration tests, they usually work with some kind of test infrastructure as well. To działanie sprawia, że są one wolniejsze i bardziej kruchy niż testy jednostkowe i integracji.This activity makes them slower and more brittle than unit and integration tests. Należy mieć tylko tyle testów funkcjonalnych, ile trzeba mieć pewność, że system zachowuje się, gdy użytkownicy oczekują.You should have only as many functional tests as you need to be confident the system is behaving as users expect.

Piramida testowaniaTesting Pyramid

Fowlera Martin na temat ostrosłupa testowego, którego przykład przedstawiono na rysunku 9-1.Martin Fowler wrote about the testing pyramid, an example of which is shown in Figure 9-1.

Piramida testowania

Rysunek 9-1.Figure 9-1. Piramida testowaniaTesting Pyramid

Różne warstwy ostrosłupa i ich względne rozmiary reprezentują różne rodzaje testów oraz liczbę elementów, które należy napisać dla aplikacji.The different layers of the pyramid, and their relative sizes, represent different kinds of tests and how many you should write for your application. Jak widać, zalecenie ma mieć dużą podstawę testów jednostkowych, które są obsługiwane przez niższą warstwę testów integracji, z jeszcze mniejszą warstwą testów funkcjonalnych.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. Każda warstwa powinna mieć idealny tylko testy, które nie mogą być wykonywane odpowiednio na niższej warstwie.Each layer should ideally only have tests in it that cannot be performed adequately at a lower layer. Zadbaj o to, aby określić, jakiego rodzaju testu potrzebujesz w konkretnym scenariuszu.Keep the testing pyramid in mind when you are trying to decide which kind of test you need for a particular scenario.

Co należy przetestowaćWhat to test

Typowy problem dla deweloperów, którzy są niedoświadczeni z pisaniem zautomatyzowanych testów, jest tworzony z przeznaczeniem do przetestowania.A common problem for developers who are inexperienced with writing automated tests is coming up with what to test. Dobrym punktem początkowym jest Testowanie logiki warunkowej.A good starting point is to test conditional logic. Wszędzie tam, gdzie masz metodę z zachowaniem, która zmienia się na podstawie instrukcji warunkowej (if-else, Switch itd.), powinna być dostępna co najmniej kilka testów, które potwierdzają poprawne zachowanie określonych warunków.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. Jeśli kod zawiera warunki błędu, warto napisać co najmniej jeden test dla "silnej ścieżki" przez kod (bez błędów) i co najmniej jeden test dla "ścieżki sad" (z błędami lub nietypowymi wynikami), aby potwierdzić, że aplikacja działa zgodnie z oczekiwaniami w przypadku błędów.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. Na koniec spróbuj skupić się na testowaniu rzeczy, które mogą się nie powieść, zamiast skupić się na metrykach, takich jak pokrycie kodu.Finally, try to focus on testing things that can fail, rather than focusing on metrics like code coverage. Większa ilość pokrycia kodu jest lepsza niż mniej, ogólnie.More code coverage is better than less, generally. Jednak zapisanie kilku dodatkowych testów złożonej i krytycznej dla firmy jest zwykle lepszym wykorzystaniem czasu niż podczas pisania testów dla właściwości autoproperties tylko w celu poprawy metryk pokrycia kodu testowego.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.

Organizowanie projektów testowychOrganizing test projects

Projekty testowe mogą być zorganizowane, jednak najlepiej sprawdzają się.Test projects can be organized however works best for you. Dobrym pomysłem jest oddzielenie testów według typu (test jednostkowy, test integracji) i ich testowanie (według projektu, według przestrzeni nazw).It's a good idea to separate tests by type (unit test, integration test) and by what they are testing (by project, by namespace). Czy ta separacja składa się z folderów w ramach pojedynczego projektu testowego lub wielu projektów testowych, stanowi decyzję projektową.Whether this separation consists of folders within a single test project, or multiple test projects, is a design decision. Jeden projekt jest najprostszy, ale w przypadku dużych projektów z wieloma testami lub w celu łatwiejszego uruchamiania różnych zestawów testów można chcieć mieć kilka różnych projektów testowych.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. Wiele zespołów organizuje projekty testowe na podstawie testowanego projektu, co w przypadku aplikacji z więcej niż kilkoma projektami może spowodować powstanie dużej liczby projektów testowych, szczególnie w przypadku, gdy te dane są nadal dzielone zgodnie z typem testów w każdym projekcie.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. Podejście do kompromisu polega na tym, że jeden projekt dla każdego rodzaju testu, na aplikację, z folderami w projektach testowych, ma wskazywać testowany projekt (i klasę).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.

Typowym podejściem jest organizowanie projektów aplikacji w folderze "src" i projektów testowych aplikacji w ramach równoległego folderu "Tests".A common approach is to organize the application projects under a 'src' folder, and the application's test projects under a parallel 'tests' folder. Można utworzyć dopasowane foldery rozwiązań w programie Visual Studio, jeśli okaże się to przydatne w tej organizacji.You can create matching solution folders in Visual Studio, if you find this organization useful.

Testowanie organizacji w rozwiązaniu

Rysunek 9-2.Figure 9-2. Testowanie organizacji w rozwiązaniuTest organization in your solution

Możesz użyć niezależnej platformy testowej.You can use whichever test framework you prefer. Środowisko xUnit Framework działa prawidłowo i jest zapisywana wszystkie ASP.NET Core i EF Core testy.The xUnit framework works well and is what all of the ASP.NET Core and EF Core tests are written in. Możesz dodać projekt testu xUnit w programie Visual Studio przy użyciu szablonu pokazanego na rysunku 9-3 lub interfejsu wiersza polecenia przy użyciu polecenia dotnet new xunit .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.

Dodawanie projektu testowego xUnit w programie Visual Studio

Rysunek 9-3.Figure 9-3. Dodawanie projektu testowego xUnit w programie Visual StudioAdd an xUnit Test Project in Visual Studio

Nazwa testuTest naming

Nazwij testy w spójny sposób, podając nazwy wskazujące, co każdy test wykonuje.Name your tests in a consistent fashion, with names that indicate what each test does. Jednym z metod, które mam doskonałe sukces, jest nazwa klasy testowej zgodnie z klasą i metodą, które są testowane.One approach I've had great success with is to name test classes according to the class and method they are testing. To podejście skutkuje wieloma niewielkimi klasami testowymi, ale wyraźnie czyści, do czego każdy test jest odpowiedzialny.This approach results in many small test classes, but it makes it extremely clear what each test is responsible for. Po skonfigurowaniu nazwy klasy testowej, aby zidentyfikować klasę i metodę do przetestowania, nazwa metody testowej może służyć do określenia testowanego zachowania.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. Ta nazwa powinna zawierać oczekiwane zachowanie i wszelkie dane wejściowe lub założenia, które powinny spowodować takie zachowanie.This name should include the expected behavior and any inputs or assumptions that should yield this behavior. Przykładowe nazwy testów:Some example test names:

  • CatalogControllerGetImage.CallsImageServiceWithId

  • CatalogControllerGetImage.LogsWarningGivenImageMissingException

  • CatalogControllerGetImage.ReturnsFileResultWithBytesGivenSuccess

  • CatalogControllerGetImage.ReturnsNotFoundResultGivenImageMissingException

Zmiana tego podejścia spowoduje zakończenie każdej klasy testowej o nazwie "powinien" i nieco modyfikuje intensywność:A variation of this approach ends each test class name with "Should" and modifies the tense slightly:

  • CatalogControllerGetImagePowinien . WywołanieImageServiceWithIdCatalogControllerGetImageShould.CallImageServiceWithId

  • CatalogControllerGetImagePowinien . DziennikWarningGivenImageMissingExceptionCatalogControllerGetImageShould.LogWarningGivenImageMissingException

Niektóre zespoły szukają drugiego podejścia do nazewnictwa, chociaż nieco bardziej pełne.Some teams find the second naming approach clearer, though slightly more verbose. W każdym przypadku spróbuj użyć konwencji nazewnictwa, która zapewnia wgląd w działanie testowe, dzięki czemu w przypadku niepowodzenia co najmniej jednego testu, jest oczywiste z nazw, których przypadki zakończyły się niepowodzeniem.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. Unikaj niejasnego nazewnictwa testów, takich jak ControllerTests. TEST1, ponieważ te nazwy nie oferują wartości, gdy są widoczne w wynikach testu.Avoid naming your tests vaguely, such as ControllerTests.Test1, as these names offer no value when you see them in test results.

Jeśli przestrzegasz konwencji nazewnictwa, takiej jak powyżej, która tworzy wiele małych klas testowych, dobrym pomysłem jest dalsze organizowanie testów przy użyciu folderów i przestrzeni nazw.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. Rysunek 9-4 przedstawia jedno podejście do organizowania testów według folderu w kilku projektach testowych.Figure 9-4 shows one approach to organizing tests by folder within several test projects.

Organizowanie klas testowych według folderu na podstawie testowanej klasy

Rysunek 9-4.Figure 9-4. Organizowanie klas testowych według folderu na podstawie testowanej klasy.Organizing test classes by folder based on class being tested.

Jeśli określona Klasa aplikacji ma wiele metod do przetestowania (i w ten sposób wiele klas testowych), warto umieścić te klasy w folderze odpowiadającym klasie aplikacji.If a particular application class has many methods being tested (and thus many test classes), it may make sense to place these classes in a folder corresponding to the application class. Ta organizacja nie różni się od sposobu, w jaki można organizować pliki w folderach w innym miejscu.This organization is no different than how you might organize files into folders elsewhere. Jeśli masz więcej niż trzy lub cztery powiązane pliki w folderze zawierającym wiele innych plików, często warto przenieść je do własnego podfolderu.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.

Testowanie jednostkowe ASP.NET Core aplikacjiUnit testing ASP.NET Core apps

W dobrze zaprojektowanej aplikacji ASP.NET Core większość złożoności i logiki biznesowej będzie hermetyzowana w jednostkach firmy i w różnych usługach.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. Sama aplikacja MVC ASP.NET Core ze swoimi kontrolerami, filtrami, modele widoków i widokami powinna wymagać kilku testów jednostkowych.The ASP.NET Core MVC app itself, with its controllers, filters, viewmodels, and views, should require few unit tests. Większość funkcjonalności danej akcji leży poza samą metodą akcji.Much of the functionality of a given action lies outside the action method itself. Testowanie, czy kierowanie i globalna obsługa błędów działają poprawnie, nie można efektywnie wykonać test jednostkowy.Testing whether routing or global error handling work correctly cannot be done effectively with a unit test. Analogicznie, wszelkie filtry, w tym Walidacja modelu i filtry uwierzytelniania i autoryzacji, nie mogą być badane jednostkowo z testem docelowym metody akcji kontrolera.Likewise, any filters, including model validation and authentication and authorization filters, cannot be unit tested with a test targeting a controller's action method. Bez tych źródeł zachowania większość metod działania powinna być bardzo mała, co pozwala na ich przetestowanie do usług, które mogą być testowane niezależnie od kontrolera, który z nich korzysta.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.

Czasami konieczne będzie Refaktoryzacja kodu w celu przetestowania go jednostkowo.Sometimes you'll need to refactor your code in order to unit test it. Często to działanie obejmuje identyfikowanie abstrakcji i użycie iniekcji zależności w celu uzyskania dostępu do abstrakcji w kodzie, który chcesz przetestować, zamiast kodowania bezpośrednio względem infrastruktury.Frequently this activity 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. Rozważmy na przykład tę prostą metodę działania do wyświetlania obrazów:For example, consider this easy 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");
}

Testy jednostkowe tej metody są utrudnione w zależności od tego System.IO.File , która z nich używa do odczytu z systemu plików.Unit testing this method is made difficult by its direct dependency on System.IO.File, which it uses to read from the file system. Możesz przetestować to zachowanie, aby upewnić się, że działa zgodnie z oczekiwaniami, ale jest to test integracji z rzeczywistymi plikami.You can test this behavior to ensure it works as expected, but doing so with real files is an integration test. Warto zauważyć, że nie można testować jednostkowo trasy tej metody, zobaczysz, — jak wkrótce wykonać te testy z testem funkcjonalnym.It's worth noting you can't unit test this method's route—you'll see how to do this testing with a functional test shortly.

Jeśli nie można bezpośrednio przetestować zachowania systemu plików i nie można przetestować trasy, co to jest?If you can't unit test the file system behavior directly, and you can't test the route, what is there to test? Ponadto po refaktoryzacji w celu przeprowadzenia testów jednostkowych można wykryć niektóre przypadki testowe i brakujące zachowanie, takie jak obsługa błędów.Well, after refactoring to make unit testing possible, you may discover some test cases and missing behavior, such as error handling. Co robią metoda po znalezieniu pliku?What does the method do when a file isn't found? Co należy zrobić?What should it do? W tym przykładzie metoda refaktoryzacji wygląda następująco: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 i _imageService są zarówno wstrzykiwane jak i zależności._logger and _imageService are both injected as dependencies. Teraz można testować, że ten sam identyfikator, który jest przesyłany do metody akcji, jest przenoszona do _imageService i że wynikowe bajty są zwracane jako część FileResult.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. Możesz również sprawdzić, czy rejestrowanie błędów odbywa się zgodnie z oczekiwaniami, a NotFound wynik jest zwracany, jeśli brakuje obrazu, przy założeniu, że to zachowanie jest ważnym zachowaniem aplikacji (czyli nie tylko kod tymczasowy dodany przez dewelopera w celu zdiagnozowania problemu).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 behavior is important application behavior (that is, not just temporary code the developer added to diagnose an issue). Rzeczywista logika pliku została przeniesiona do oddzielnej usługi implementacji i została uzupełniona, aby zwracała wyjątek specyficzny dla aplikacji w przypadku brakujących plików.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. Tę implementację można przetestować niezależnie przy użyciu testu integracji.You can test this implementation independently, using an integration test.

W większości przypadków należy użyć globalnych programów obsługi wyjątków na kontrolerach, więc ilość logiki w nich powinna być minimalna i prawdopodobnie nie być testowana.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. Wykonaj większość testów akcji kontrolera przy użyciu testów funkcjonalnych i TestServer klasy opisanej poniżej.Do most of your testing of controller actions using functional tests and the TestServer class described below.

Testowanie integracji ASP.NET Core aplikacjeIntegration testing ASP.NET Core apps

Większość testów integracji w aplikacjach ASP.NET Core należy przetestować usługi i inne typy implementacji zdefiniowane w projekcie infrastruktury.Most of the integration tests in your ASP.NET Core apps should be testing services and other implementation types defined in your Infrastructure project. Można na przykład sprawdzić, czy EF Core pomyślnie zaktualizować i pobrać dane, których oczekujesz od klas dostępu do danych znajdujących się w projekcie infrastruktury.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. Najlepszym sposobem, aby sprawdzić, czy projekt ASP.NET Core MVC działa prawidłowo, jest testami funkcjonalnymi uruchamianymi względem aplikacji działającej na hoście testowym.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.

Testowanie funkcjonalne ASP.NET Core aplikacjiFunctional testing ASP.NET Core apps

W przypadku aplikacji ASP.NET Core TestServer Klasa sprawia, że testy funkcjonalne są dość łatwe do zapisu.For ASP.NET Core applications, the TestServer class makes functional tests fairly easy to write. Można skonfigurować TestServer za pomocą WebHostBuilder (lub HostBuilder ) bezpośrednio (jak zwykle w przypadku aplikacji) lub z WebApplicationFactory typem (dostępnym od wersji 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). Spróbuj dokładnie dopasować hosta testowego do hosta produkcyjnego, aby testy były wykonywane podobnie jak w środowisku produkcyjnym.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. WebApplicationFactoryKlasa jest przydatna do konfigurowania ContentRoot TestServer, który jest używany przez ASP.NET Core do lokalizowania zasobów statycznych, takich jak widoki.The WebApplicationFactory class is helpful for configuring the TestServer's ContentRoot, which is used by ASP.NET Core to locate static resource like Views.

Można utworzyć proste testy funkcjonalne przez utworzenie klasy testowej implementującej IClassFixture\<WebApplicationFactory\<TEntry>> , gdzie TEntry jest klasą aplikacji sieci Web Startup .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. W tym interfejsie, armatura testowa może utworzyć klienta przy użyciu CreateClient metody fabryki:With this interface 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 BasicWebTests(WebApplicationFactory<Startup> factory)
    {
        _client = factory.CreateClient();
    }

    // write tests that use _client
}

Często podczas każdego przebiegu testowego należy wykonać dodatkową konfigurację lokacji, na przykład Konfigurując aplikację do używania magazynu danych w pamięci, a następnie wypełniania aplikacji danymi testowymi.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. Aby osiągnąć tę funkcję, Utwórz własną podklasę WebApplicationFactory\<TEntry> i Zastąp jej ConfigureWebHost metodę.To achieve this functionality, create your own subclass of WebApplicationFactory\<TEntry> and override its ConfigureWebHost method. Poniższy przykład pochodzi z projektu eShopOnWeb FunctionalTests i jest używany jako część testów w głównej aplikacji sieci Web.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}");
                    }
                }
            });
        }
    }
}

Testy mogą korzystać z tego niestandardowego WebApplicationFactory przy użyciu go do tworzenia klienta, a następnie do wykonywania żądań do aplikacji przy użyciu tego wystąpienia klienta.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. Aplikacja będzie zawierać dane, które mogą być używane jako część zatwierdzeń testu.The application will have data seeded that can be used as part of the test's assertions. Poniższy test sprawdza, czy Strona główna aplikacji eShopOnWeb jest poprawnie załadowana i zawiera listę produktów, która została dodana do aplikacji w ramach danych inicjatora.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);
        }
    }
}

Ten test funkcjonalny wykonuje pełny ASP.NET Core stos aplikacji MVC/Razor Pages, w tym wszystkie oprogramowanie pośredniczące, filtry i powiązania, które mogą być stosowane.This functional test exercises the full ASP.NET Core MVC / Razor Pages application stack, including all middleware, filters, and binders that may be in place. Sprawdza, czy dana trasa ("/") zwraca oczekiwany kod stanu sukcesu i dane wyjściowe HTML.It verifies that a given route ("/") returns the expected success status code and HTML output. Nie konfiguruje prawdziwy serwer sieci Web i pozwala uniknąć większości brittleness, które mogą korzystać z rzeczywistego serwera sieci Web do testowania (na przykład problemy z ustawieniami zapory).It does so without setting up a real web server, and avoids much of the brittleness that using a real web server for testing can experience (for example, problems with firewall settings). Testy funkcjonalne uruchamiane względem TestServer są zwykle wolniejsze niż testy integracji i jednostkowe, ale są znacznie szybsze niż testy, które byłyby wykonywane przez sieć, do testowego serwera sieci Web.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. Użyj testów funkcjonalnych, aby upewnić się, że stos frontonu aplikacji działa zgodnie z oczekiwaniami.Use functional tests to ensure your application's front-end stack is working as expected. Te testy są szczególnie przydatne w przypadku znalezienia duplikatów na kontrolerach lub stronach i poprawnego duplikowania przez dodanie filtrów.These tests are especially useful when you find duplication in your controllers or pages and you address the duplication by adding filters. W idealnym przypadku ten Refaktoryzacja nie zmienia zachowania aplikacji, a zestaw testów funkcjonalnych sprawdzi, czy jest to przypadek.Ideally, this refactoring won't change the behavior of the application, and a suite of functional tests will verify this is the case.

References — testowanie ASP.NET Core aplikacji MVCReferences – Test ASP.NET Core MVC apps