Testar aplicativos ASP.NET Core MVCTest ASP.NET Core MVC apps

"Se você não gostar de realizar o teste de unidade em seu produto, provavelmente, seus clientes não vão gostar de testá-lo também.""If you don't like unit testing your product, most likely your customers won't like to test it, either." _ – Anônimo –_- Anonymous-

Um software de qualquer complexidade pode falhar de maneiras inesperadas em resposta a alterações.Software of any complexity can fail in unexpected ways in response to changes. Portanto, depois de fazer alterações, é necessário realizar um teste em todos os aplicativos, exceto os mais triviais (ou menos críticos).Thus, testing after making changes is required for all but the most trivial (or least critical) applications. O teste manual é a maneira mais lenta, menos confiável e mais cara de testar um software.Manual testing is the slowest, least reliable, most expensive way to test software. Se os aplicativos não forem projetados para serem testáveis, esse poderá ser o único meio disponível.Unfortunately, if applications aren't designed to be testable, it can be the only means available. Os aplicativos escritos de acordo com os princípios de arquitetura apresentados no capítulo 4 podem ser submetidos a um teste de unidade, além disso os aplicativos ASP.NET Core também são compatíveis com testes funcionais e de integração automatizados.Applications written following the architectural principles laid out in chapter 4 should be unit testable, and ASP.NET Core applications support automated integration and functional testing as well.

Tipos de testes automatizadosKinds of automated tests

Há muitos tipos de testes automatizados para aplicativos de software.There are many kinds of automated tests for software applications. O teste mais simples de nível mais baixo é o teste de unidade.The simplest, lowest level test is the unit test. Em um nível ligeiramente superior, há testes de integração e testes funcionais.At a slightly higher level there are integration tests and functional tests. Outros tipos de testes, como testes de interface do usuário, testes de carga, testes de estresse e smoke tests, estão além do escopo deste documento.Other kinds of tests, like UI tests, load tests, stress tests, and smoke tests, are beyond the scope of this document.

Testes de unidadeUnit tests

Um teste de unidade testa uma única parte da lógica do aplicativo.A unit test tests a single part of your application's logic. Ainda podemos descrevê-lo listando algumas das coisas que ele não é.One can further describe it by listing some of the things that it isn't. Um teste de unidade não testa como o código funciona com as dependências ou a infraestrutura – é para isso que servem os testes de integração.A unit test doesn't test how your code works with dependencies or infrastructure – that's what integration tests are for. Um teste de unidade não testa a estrutura na qual o código foi escrito – você deve pressupor que ela funciona ou, se achar que não, registre um bug e codifique uma solução alternativa.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. Um teste de unidade é executado completamente na memória e no processo.A unit test runs completely in memory and in process. Ele não se comunica com o sistema de arquivos, a rede ou um banco de dados.It doesn't communicate with the file system, the network, or a database. Os testes de unidade devem testar apenas o código.Unit tests should only test your code.

Os testes de unidade, devido ao fato de testarem apenas uma única unidade do código, sem dependências externas, devem ser executados muito rapidamente.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. Portanto, você deve conseguir executar conjuntos de testes de centenas de testes de unidade em alguns segundos.Thus, you should be able to run test suites of hundreds of unit tests in a few seconds. Execute-os com frequência, de preferência, antes de cada push para um repositório de controle do código-fonte compartilhado e, certamente, a cada build automatizado no servidor de build.Run them frequently, ideally before every push to a shared source control repository, and certainly with every automated build on your build server.

Testes de integraçãoIntegration tests

Embora seja uma boa ideia encapsular o código que interage com a infraestrutura, como bancos de dados e sistemas de arquivos, você ainda terá uma parte do código e, provavelmente, desejará testá-lo.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. Além disso, você deve verificar se as camadas do código interagem conforme esperado quando as dependências do aplicativo são totalmente resolvidas.Additionally, you should verify that your code's layers interact as you expect when your application's dependencies are fully resolved. Essa é a responsabilidade dos testes de integração.This is the responsibility of integration tests. Os testes de integração tendem a ser mais lentos e mais difíceis de serem configurados do que os testes de unidade, porque geralmente dependem de infraestrutura e dependências externas.Integration tests tend to be slower and more difficult to set up than unit tests, because they often depend on external dependencies and infrastructure. Portanto, você deve evitar testar coisas que podem ser testadas com testes de unidade em testes de integração.Thus, you should avoid testing things that could be tested with unit tests in integration tests. Se puder testar determinado cenário com um teste de unidade, você deverá testá-lo com um teste de unidade.If you can test a given scenario with a unit test, you should test it with a unit test. Caso contrário, considere o uso de um teste de integração.If you can't, then consider using an integration test.

Os testes de integração costumam ter procedimentos de instalação e de desinstalação mais complexos em comparação com os testes de unidade.Integration tests will often have more complex setup and teardown procedures than unit tests. Por exemplo, um teste de integração feito em um banco de dados real precisará de uma maneira de retornar o banco de dados para um estado conhecido antes de cada execução de teste.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. Conforme novos testes forem adicionados e o esquema de banco de dados de produção evoluir, esses scripts de teste tenderão a aumentar em tamanho e complexidade.As new tests are added and the production database schema evolves, these test scripts will tend to grow in size and complexity. Em muitos sistemas grandes, não é prático executar conjuntos completos de testes de integração em estações de trabalho do desenvolvedor antes de fazer check-in das alterações no controle do código-fonte compartilhado.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. Nesses casos, os testes de integração podem ser executados em um servidor de build.In these cases, integration tests may be run on a build server.

Testes funcionaisFunctional tests

Os testes de integração são escritos da perspectiva do desenvolvedor, para verificar se alguns componentes do sistema funcionam corretamente juntos.Integration tests are written from the perspective of the developer, to verify that some components of the system work correctly together. Os testes funcionais são escritos da perspectiva do usuário e verificam a correção do sistema com base em seus requisitos.Functional tests are written from the perspective of the user, and verify the correctness of the system based on its requirements. O trecho a seguir oferece uma analogia útil de como considerar os testes funcionais, em comparação com os testes de unidade:The following excerpt offers a useful analogy for how to think about functional tests, compared to unit tests:

"Muitas vezes, o desenvolvimento de um sistema é comparado à construção de uma casa."Many times the development of a system is likened to the building of a house. Embora essa analogia não seja muito correta, podemos estendê-la para compreender a diferença entre os testes de unidade e os testes funcionais.While this analogy isn't quite correct, we can extend it for the purposes of understanding the difference between unit and functional tests. O teste de unidade se assemelha a um inspetor de construção visitando o canteiro de obras de uma casa.Unit testing is analogous to a building inspector visiting a house's construction site. Ele está concentrado nos vários sistemas internos da casa, na base, na estrutura, na parte elétrica, no encanamento e assim por diante.He is focused on the various internal systems of the house, the foundation, framing, electrical, plumbing, and so on. Ele garante (testa) que as partes da casa funcionarão corretamente e com segurança, ou seja, seguirão o código de construção.He ensures (tests) that the parts of the house will work correctly and safely, that is, meet the building code. Os testes funcionais, neste cenário, se assemelham ao proprietário da casa visitando esse mesmo canteiro de obras.Functional tests in this scenario are analogous to the homeowner visiting this same construction site. Ele pressupõe que os sistemas internos se comportarão corretamente e que o inspetor de construção está realizando sua tarefa.He assumes that the internal systems will behave appropriately, that the building inspector is performing his task. O proprietário da casa está voltado para a ideia de como será morar nessa casa.The homeowner is focused on what it will be like to live in this house. Ele está preocupado com a aparência da casa, se os vários quartos têm um tamanho confortável, se a casa atende às necessidades da família, se as janelas estão em um bom lugar para capturar o sol da manhã.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. O proprietário da casa está realizando testes funcionais na casa.The homeowner is performing functional tests on the house. Ele tem a perspectiva do usuário.He has the user's perspective. O inspetor de construção está realizando testes de unidade na casa.The building inspector is performing unit tests on the house. Ele tem a perspectiva do construtor."He has the builder's perspective."

Fonte: Teste de unidade versus testes funcionaisSource: Unit Testing versus Functional Tests

Gosto de dizer que "como desenvolvedores, falhamos de duas maneiras: criamos a coisa da maneira errada ou criamos a coisa errada".I'm fond of saying "As developers, we fail in two ways: we build the thing wrong, or we build the wrong thing." Os testes de unidade garantem que você está criando a coisa da maneira certa; os testes funcionais garantem que você está criando a coisa certa.Unit tests ensure you are building the thing right; functional tests ensure you are building the right thing.

Como os testes funcionais operam no nível do sistema, eles podem exigir um certo grau de automação da interface do usuário.Since functional tests operate at the system level, they may require some degree of UI automation. Assim como os testes de integração, elas geralmente funcionam com algum tipo de infraestrutura de teste também.Like integration tests, they usually work with some kind of test infrastructure as well. Isso faz com que eles fiquem mais lentos e mais frágeis do que os testes de unidade e de integração.This makes them slower and more brittle than unit and integration tests. Você deve ter somente a quantidade de testes funcionais de que precisa para ter certeza de que o sistema está se comportando conforme esperado pelos usuários.You should have only as many functional tests as you need to be confident the system is behaving as users expect.

Pirâmide de testesTesting Pyramid

Martin Fowler escreveu sobre a pirâmide de testes, cujo exemplo é mostrado na Figura 9-1.Martin Fowler wrote about the testing pyramid, an example of which is shown in Figure 9-1.

Pirâmide de testes

Figura 9-1.Figure 9-1. Pirâmide de testesTesting Pyramid

As diferentes camadas da pirâmide e seus tamanhos relativos representam tipos diferentes de testes e quantos você deve gravar para seu aplicativo.The different layers of the pyramid, and their relative sizes, represent different kinds of tests and how many you should write for your application. Como você pode ver, a recomendação é ter uma grande base de testes de unidade, apoiada por uma camada menor de testes de integração, com uma camada ainda menor de testes funcionais.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. O ideal é que cada camada tenha somente testes que não podem ser realizados de forma adequada em uma camada inferior.Each layer should ideally only have tests in it that cannot be performed adequately at a lower layer. Lembre-se da pirâmide de testes quando estiver tentando decidir qual tipo de teste é necessário para um cenário específico.Keep the testing pyramid in mind when you are trying to decide which kind of test you need for a particular scenario.

O que testarWhat to test

Um problema comum para os desenvolvedores que não têm experiência com a criação de testes automatizados é decidir o que testar.A common problem for developers who are inexperienced with writing automated tests is coming up with what to test. Um bom ponto de partida é testar a lógica condicional do teste.A good starting point is to test conditional logic. Sempre que você tiver um método com um comportamento que é alterado de acordo com uma instrução condicional (if-else, alternância, etc.), você deverá pensar em, pelo menos, dois testes que confirmem esse comportamento correto em determinadas condições.Anywhere you have a method with behavior that changes based on a conditional statement (if-else, switch, etc.), you should be able to come up at least a couple of tests that confirm the correct behavior for certain conditions. Se o código apresentar condições de erro, será melhor gravar, pelo menos, um teste para o "caminho certo" pelo código (sem erros) e, pelo menos, um teste para o "caminho errado" (com erros ou resultados atípicos) para confirmar que o aplicativo se comporta conforme esperado em caso de erros.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. Por fim, tente se concentrar no teste de coisas que podem falhar, em vez de se concentrar em métricas como cobertura de código.Finally, try to focus on testing things that can fail, rather than focusing on metrics like code coverage. Em geral, mais cobertura de código é melhor do que menos.More code coverage is better than less, generally. No entanto, é melhor utilizar o tempo criando mais alguns testes de um método muito complexo e comercialmente crítico do que criando testes para propriedades automáticas, apenas para melhorar as métricas de cobertura de código de teste.However, writing a few more tests of a very 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.

Organizando projetos de testeOrganizing test projects

Os projetos de teste podem ser organizados da maneira mais adequada à sua situação.Test projects can be organized however works best for you. É uma boa ideia separar os testes por tipo (teste de unidade, teste de integração) e pelo que eles estão testando (por projeto, por namespace).It's a good idea to separate tests by type (unit test, integration test) and by what they are testing (by project, by namespace). Indicar se essa separação consiste em pastas dentro de um único projeto de teste ou de vários projetos de teste é uma decisão de design.Whether this separation consists of folders within a single test project, or multiple test projects, is a design decision. Um projeto é o mais simples, mas para projetos grandes com muitos testes ou para executar diferentes conjuntos de testes com mais facilidade, talvez você deseje ter vários projetos de teste diferentes.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. Muitas equipes organizam projetos de teste com base no projeto que estão testando, o que, para aplicativos com mais de alguns projetos, pode resultar em um grande número de projetos de teste, especialmente se você ainda divide-os de acordo com o tipo de testes existente em cada projeto.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. Uma abordagem de meio-termo é ter um projeto por tipo de teste, por aplicativo, com pastas dentro dos projetos de teste para indicar o projeto (e a classe) que está sendo testado.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.

Uma abordagem comum é organizar os projetos de aplicativo em uma pasta 'src' e os projetos de teste do aplicativo em uma pasta 'tests' paralela.A common approach is to organize the application projects under a ‘src' folder, and the application's test projects under a parallel ‘tests' folder. Crie pastas de solução correspondentes no Visual Studio se achar esta organização útil.You can create matching solution folders in Visual Studio, if you find this organization useful.

Testar a organização em sua solução

Figura 9-2.Figure 9-2. Testar a organização em sua soluçãoTest organization in your solution

Você pode usar qualquer estrutura de teste que preferir.You can use whichever test framework you prefer. A estrutura xUnit funciona bem e é nela em que todos os testes do ASP.NET Core e do EF Core são criados.The xUnit framework works well and is what all of the ASP.NET Core and EF Core tests are written in. Você pode adicionar um projeto de teste do xUnit no Visual Studio usando o modelo mostrado na Figura 9-3 ou por meio da CLI usando o comando 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.

Adicionar um projeto de teste do xUnit no Visual Studio

Figura 9-3.Figure 9-3. Adicionar um projeto de teste do xUnit no Visual StudioAdd an xUnit Test Project in Visual Studio

Nomenclatura de testesTest naming

Você deve nomear os testes de maneira consistente, com nomes que indicam o que cada teste faz.You should name your tests in a consistent fashion, with names that indicate what each test does. Uma abordagem na qual tive grande sucesso é nomear as classes de teste de acordo com a classe e o método que elas estão testando.One approach I've had great success with is to name test classes according to the class and method they are testing. Isso resulta em muitas classes de teste pequenas, mas deixa extremamente claro pelo que é responsável cada teste.This results in many small test classes, but it makes it extremely clear what each test is responsible for. Com o nome da classe de teste configurado para identificar a classe e o método a serem testados, o nome do método de teste pode ser usado para especificar o comportamento que está sendo testado.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. Isso deve incluir o comportamento esperado e as entradas ou suposições que devem gerar esse comportamento.This should include the expected behavior and any inputs or assumptions that should yield this behavior. Alguns nomes de teste de exemplo:Some example test names:

  • CatalogControllerGetImage.CallsImageServiceWithId

  • CatalogControllerGetImage.LogsWarningGivenImageMissingException

  • CatalogControllerGetImage.ReturnsFileResultWithBytesGivenSuccess

  • CatalogControllerGetImage.ReturnsNotFoundResultGivenImageMissingException

Uma variação dessa abordagem termina o nome de cada classe de teste com "Should" e modifica ligeiramente os tempos verbais:A variation of this approach ends each test class name with "Should" and modifies the tense slightly:

  • CatalogControllerGetImageShould.CallImageServiceWithIdCatalogControllerGetImageShould.CallImageServiceWithId

  • CatalogControllerGetImageShould.LogWarningGivenImageMissingExceptionCatalogControllerGetImageShould.LogWarningGivenImageMissingException

Algumas equipes consideram a segunda abordagem de nomenclatura mais clara, embora um pouco mais detalhada.Some teams find the second naming approach clearer, though slightly more verbose. De qualquer forma, tente usar uma convenção de nomenclatura que fornece informações sobre o comportamento de teste, de modo que quando um ou mais testes falharem, seja óbvio descobrir, com base nos nomes, quais casos falharam.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. Evite nomear os testes de modo vago, como ControllerTests.Test1, pois essa nomenclatura não oferece nenhum valor quando você vê esses nomes em resultados de teste.Avoid naming you tests vaguely, such as ControllerTests.Test1, as these offer no value when you see them in test results.

Se você segue uma convenção de nomenclatura como a mostrada acima, que produz muitas classes de teste pequenas, é uma boa ideia organizar ainda mais os testes usando pastas e namespaces.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. A Figura 9-4 mostra uma abordagem para organizar os testes por pasta dentro de vários projetos de teste.Figure 9-4 shows one approach to organizing tests by folder within several test projects.

Organizando classes de teste por pasta com base na classe que está sendo testada

Figura 9-4.Figure 9-4. Organizando classes de teste por pasta com base na classe que está sendo testada.Organizing test classes by folder based on class being tested.

É claro que, se uma classe de aplicativo específica tiver muitos métodos que estão sendo testados (e, portanto, muitas classes de teste), talvez faça sentido colocá-los em uma pasta correspondente à classe de aplicativo.Of course, 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. Essa organização não é diferente da forma como você pode organizar arquivos em pastas em outro lugar.This organization is no different than how you might organize files into folders elsewhere. Caso você tenha mais de três ou quatro arquivos relacionados em uma pasta que contém muitos outros arquivos, geralmente, é útil movê-los para sua própria subpasta.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.

Realizando teste de unidade em aplicativos ASP.NET CoreUnit testing ASP.NET Core apps

Em um aplicativo ASP.NET Core bem projetado, a maior parte da complexidade e da lógica de negócios será encapsulada em entidades de negócios e uma variedade de serviços.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. O aplicativo ASP.NET Core MVC em si, com seus controladores, filtros, modelos de exibição e exibições, deve exigir muito poucos testes de unidade.The ASP.NET Core MVC app itself, with its controllers, filters, viewmodels, and views, should require very few unit tests. Grande parte da funcionalidade de determinada ação se encontra fora do próprio método de ação.Much of the functionality of a given action lies outside the action method itself. O teste para saber se o roteamento funciona corretamente, ou o tratamento de erro global, não pode ser feito efetivamente com um teste de unidade.Testing whether routing works correctly, or global error handling, cannot be done effectively with a unit test. Da mesma forma, os filtros, incluindo filtros de validação de modelo e autenticação e autorização, não podem ser testados com testes de unidade.Likewise, any filters, including model validation and authentication and authorization filters, cannot be unit tested. Sem essas fontes de comportamento, a maioria dos métodos de ação deve ser insignificantemente pequena, com a delegação da maior parte de seu trabalho para serviços que podem ser testados sem depender do controlador que os usa.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.

Às vezes, você precisará refatorar o código para submetê-lo ao teste de unidade.Sometimes you'll need to refactor your code in order to unit test it. Com frequência, isso envolve a identificação de abstrações e o uso da injeção de dependência para acessar a abstração no código que você deseja testar, em vez da codificação direta na infraestrutura.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. Por exemplo, considere este método de ação simples para a exibição de imagens: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");
}

Submeter esse método ao teste de unidade é dificultado por sua dependência direta de System.IO.File, que ele usa para ler o sistema de arquivos.Unit testing this method is made difficult by its direct dependency on System.IO.File, which it uses to read from the file system. Você pode testar esse comportamento para garantir que ele funciona conforme esperado, mas fazer isso com arquivos reais é um teste de integração.You can test this behavior to ensure it works as expected, but doing so with real files is an integration test. Vale a pena observar que não é possível submeter a rota desse método a um teste de unidade. Você verá como fazer isso com um teste funcional em breve.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.

Se você não pode realizar o teste de unidade no comportamento do sistema de arquivos diretamente e não pode testar a rota, o que há para testar?If you can't unit test the file system behavior directly, and you can't test the route, what is there to test? Bem, depois de fazer a refatoração para possibilitar o teste de unidade, talvez você descubra alguns casos de teste e um comportamento ausente, como o tratamento de erro.Well, after refactoring to make unit testing possible, you may discover some test cases and missing behavior, such as error handling. O que o método faz quando um arquivo não é encontrado?What does the method do when a file isn't found? O que ele deve fazer?What should it do? Neste exemplo, o método refatorado tem esta aparência: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");
}

O _agente e o _imageService são injetados como dependências.The _logger and _imageService are both injected as dependencies. Agora você pode testar se a mesma ID passada para o método de ação é passada para o _imageService, e se os bytes resultantes são retornados como parte do FileResult.Now you can test that the same id that is passed to the action method is passed to the _imageService, and that the resulting bytes are returned as part of the FileResult. Você também pode testar se o log de erros ocorre conforme esperado e se um resultado NotFound é retornado caso a imagem esteja ausente, supondo que esse seja um comportamento importante do aplicativo (ou seja, não apenas um código temporário adicionado pelo desenvolvedor para diagnosticar um problema).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). A lógica real do arquivo foi movida para um serviço de implementação separado e foi aumentada para retornar uma exceção específica do aplicativo para o caso de um arquivo ausente.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. Você pode testar essa implementação de forma independente, usando um teste de integração.You can test this implementation independently, using an integration test.

Na maioria dos casos, será melhor usar manipuladores de exceção globais em seus controladores, portanto, a quantidade de lógica contida neles será mínima e provavelmente não valerá a pena usar testes de unidade.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. Você deve executar a maior parte dos testes de ações do controlador usando testes funcionais e a classe TestServer descrita abaixo.You should do most of your testing of controller actions using functional tests and the TestServer class described below.

Realizando testes de integração em aplicativos ASP.NET CoreIntegration testing ASP.NET Core apps

A maioria dos testes de integração em seus aplicativos ASP.NET Core deve testar serviços e outros tipos de implementação definidos no projeto de infraestrutura.Most of the integration tests in your ASP.NET Core apps should be testing services and other implementation types defined in your Infrastructure project. Por exemplo, você poderia testar se o EF Core estava atualizando e recuperando com êxito os dados que você espera de suas classes de acesso a dados que residem no projeto de infraestrutura.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. A melhor maneira de testar se o projeto ASP.NET Core MVC está se comportando corretamente é com testes funcionais executados no aplicativo em execução em um host de teste.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.

Realizando teste funcional em aplicativos ASP.NET CoreFunctional testing ASP.NET Core apps

Para os aplicativos ASP.NET Core, a classe TestServer facilita bastante a escrita de testes funcionais.For ASP.NET Core applications, the TestServer class makes functional tests fairly easy to write. Você configura um TestServer usando um WebHostBuilder diretamente (como faz normalmente com seu aplicativo) ou com o tipo WebApplicationFactory (disponível desde a versão 2.1).You configure a TestServer using a WebHostBuilder directly (as you normally do for your application), or with the WebApplicationFactory type (available since version 2.1). Você deve tentar corresponder o host de teste ao host de produção o máximo possível, para que seus testes tenham um comportamento semelhante ao que o aplicativo terá em produção.You should try to match your test host to your production host as closely as possible, so your tests will exercise behavior similar to what the app will do in production. A classe WebApplicationFactory é útil para a configuração de ContentRoot do TestServer, que é usada pelo ASP.NET Core para localizar recursos estáticos, como Exibições.The WebApplicationFactory class is helpful for configuring the TestServer's ContentRoot, which is used by ASP.NET Core to locate static resource like Views.

Você pode criar testes funcionais simples criando uma classe de teste que implementa IClassFixture<WebApplicationFactory<TEntry>>, em que TEntry é a classe de inicialização do aplicativo Web.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. Com isso em vigor, o acessório de teste pode criar um cliente usando o método CreateClient do alocador: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
}

Geralmente é necessário executar alguma configuração adicional do site antes da execução de cada teste, como configurar o aplicativo para usar um armazenamento de dados na memória e, em seguida, propagar o aplicativo com os dados de teste.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. Para fazer isso, você deve criar sua própria subclasse de WebApplicationFactory<TEntry> e substituir seu método ConfigureWebHost.To do this, you should create your own subclass of WebApplicationFactory<TEntry> and override its ConfigureWebHost method. O exemplo a seguir é do projeto FunctionalTests de eShopOnWeb e é usado como parte dos testes no aplicativo Web principal.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.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.Controllers
{
    public class CustomWebApplicationFactory<TStartup>
    : WebApplicationFactory<Startup>
    {
        protected override void ConfigureWebHost(IWebHostBuilder builder)
        {
            builder.ConfigureServices(services =>
            {
                // Create a new service provider.
                var serviceProvider = new ServiceCollection()
                    .AddEntityFrameworkInMemoryDatabase()
                    .BuildServiceProvider();

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

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

                // 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<CustomWebApplicationFactory<TStartup>>>();

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

                    try
                    {
                        // Seed the database with test data.
                        CatalogContextSeed.SeedAsync(db, loggerFactory).Wait();
                    }
                    catch (Exception ex)
                    {
                        logger.LogError(ex, $"An error occurred seeding the " +
                            "database with test messages. Error: {ex.Message}");
                    }
                }
            });
        }
    }
}

Os testes podem usar esse WebApplicationFactory personalizado para criar um cliente e, em seguida, fazer solicitações ao aplicativo usando essa instância do cliente.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. O aplicativo terá dados propagados que poderão ser usados como parte das asserções do teste.The application will have data seeded that can be used as part of the test's assertions. O teste a seguir verifica se a home page do aplicativo eShopOnWeb é carregada corretamente e inclui uma listagem de produtos que foi adicionada ao aplicativo como parte dos dados de semente.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.Controllers;
using Microsoft.eShopWeb.Web;
using System.Net.Http;
using System.Threading.Tasks;
using Xunit;

namespace Microsoft.eShopWeb.FunctionalTests.WebRazorPages
{
    public class HomePageOnGet : IClassFixture<CustomWebApplicationFactory<Startup>>
    {
        public HomePageOnGet(CustomWebApplicationFactory<Startup> 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);
        }
    }
}

Esse teste funcional emprega a pilha completa do aplicativo ASP.NET Core MVC/Razor Pages, incluindo todos os middlewares, filtros, associadores e outros que possam estar em vigor.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. Ele verifica se uma determinada rota ("/") retorna o código de status de êxito esperado e a saída HTML.It verifies that a given route ("/") returns the expected success status code and HTML output. Ele faz isso sem configurar um servidor Web real e, portanto, evita grande parte da fragilidade decorrente do uso de um servidor Web real (por exemplo, problemas com as configurações de 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). Em geral, os testes funcionais executados no TestServer são mais lentos do que os testes de integração e de unidade, mas são muito mais rápidos do que os testes que seriam executados na rede em um servidor Web de teste.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. Use os testes funcionais para garantir a que pilha de front-end do aplicativo funcione conforme o esperado.You should use functional tests to ensure your application's front-end stack is working as expected. Esses testes são úteis principalmente quando você encontra duplicação em seus controladores ou páginas e soluciona a duplicação adicionando filtros.These tests are especially useful when you find duplication in your controllers or pages and you address the duplication by adding filters. O ideal é que essa refatoração não altere o comportamento do aplicativo. Um conjunto de testes funcionais pode verificar isso.Ideally, this refactoring won't change the behavior of the application, and a suite of functional tests will verify this is the case.

Referências – Testar aplicativos ASP.NET Core MVCReferences – Test ASP.NET Core MVC apps