Test delle app Web e dei servizi ASP.NET di base
Suggerimento
Questo contenuto è un estratto da eBook, architettura di microservizi .NET per applicazioni .NET in contenitori, disponibili in .NET Docs o come PDF scaricabile gratuitamente che può essere letto offline.
I controller sono un componente essenziale di qualsiasi servizio API ASP.NET Core e applicazione Web MVC ASP.NET. Di conseguenza è importante verificare che funzionino correttamente nell'applicazione. I test automatizzati eseguono questa verifica e rilevano gli errori dei controller prima che questi raggiungano la fase di produzione.
È necessario testare il comportamento del controller in base a input valido o non validi ed eseguire il test delle risposte del controller in base ai risultati dell'operazione di business effettuata. Tuttavia, è necessario avere i seguenti tipi di test nei microservizi:
Unit test. Questi test garantiscono che i singoli componenti dell'applicazione funzionino come previsto. Le asserzioni testano l'API componente.
Test di integrazione. Questi test garantiscono che le interazioni dei componenti funzionino come previsto rispetto agli artefatti esterni come i database. Le asserzioni possono testare l'API componente, l'interfaccia utente o gli effetti collaterali di azioni come input/output nei database, registrazione e così via.
Test funzionali per ogni microservizio. Questi test garantiscono che l'applicazione funzioni come previsto dal punto di vista dell'utente.
Test di servizio. Questi test garantiscono che i casi d'uso end-to-end, inclusi i test di più servizi contemporaneamente, vengano testati. Per questo tipo di test, è necessario preparare prima l'ambiente. In questo caso, ciò significa avviare i servizi (ad esempio, usando il comando docker-compose up).
Implementazione di unit test per le API Web ASP.NET Core
Lo unit test prevede l'esecuzione di test su una parte di un'applicazione isolandola dall'infrastruttura e dalle dipendenze. Quando si sottopone a unit test la logica del controller, si verifica solo il contenuto di una singola azione o metodo e non il comportamento delle relative dipendenze o del framework. Gli unit test non rilevano i problemi dell'interazione tra componenti: a questo scopo esistono i test di integrazione.
Quando si sottopongono a unit test le azioni del controller, è importante concentrarsi esclusivamente sul funzionamento del relativo controller. Uno unit test del controller consente di evitare, ad esempio, filtri, routing o associazione di modelli (il mapping dei dati di richiesta a un elemento ViewModel o DTO). Gli unit test risultano in genere facili da creare e rapidi da eseguire, perché verificano un solo aspetto. Un set di unit test ben organizzato può essere eseguito spesso senza sovraccarichi eccessivi.
Gli unit test vengono implementati in base ai framework di test come xUnit.net, MSTest, Moq o NUnit. Per l'applicazione di esempio eShopOnContainers, useremo xUnit.
Quando si scrive un unit test per un controller API Web, creare un'istanza della classe controller direttamente usando la nuova parola chiave in C#, in modo che il test venga eseguito il più rapidamente possibile. L'esempio seguente illustra come eseguire questa operazione usando xUnit come framework di test.
[Fact]
public async Task Get_order_detail_success()
{
//Arrange
var fakeOrderId = "12";
var fakeOrder = GetFakeOrder();
//...
//Act
var orderController = new OrderController(
_orderServiceMock.Object,
_basketServiceMock.Object,
_identityParserMock.Object);
orderController.ControllerContext.HttpContext = _contextMock.Object;
var actionResult = await orderController.Detail(fakeOrderId);
//Assert
var viewResult = Assert.IsType<ViewResult>(actionResult);
Assert.IsAssignableFrom<Order>(viewResult.ViewData.Model);
}
Implementazione dei test funzionali e di integrazione per ogni microservizio
Come già indicato, i test di integrazione e i test funzionali hanno scopi e obiettivi diversi. Tuttavia, la modalità di implementazione di entrambi durante il test dei controller ASP.NET Core è simile, dunque questa sezione è incentrata sui test di integrazione.
Il test di integrazione garantisce che i componenti di un'applicazione funzionino correttamente quando assemblati. ASP.NET Core supporta i test di integrazione con framework di unit test e un host Web di test predefinito che può essere usato per gestire le richieste senza sovraccarico di rete.
A differenza degli unit test, i test di integrazione comportano spesso preoccupazioni relative all'infrastruttura dell'applicazione, ad esempio il database, il file system, le risorse di rete o le richieste e risposte Web. Gli unit test usano dati falsi o oggetti fittizi al posto di queste preoccupazioni, ma lo scopo dei test di integrazione è confermare il funzionamento previsto del sistema, dunque è inutile usare dati falsi oppure oggetti fittizi. Si includerà piuttosto l'infrastruttura, come l'accesso al database o la chiamata di un servizio da altri servizi.
Visto che esercitano segmenti di codice più grandi rispetto agli unit test e si basano sugli elementi dell'infrastruttura, i test di integrazione tendono a essere notevolmente più lenti rispetto agli unit test. Di conseguenza, è consigliabile limitare il numero di test di integrazione da scrivere ed eseguire.
ASP.NET Core include un host Web di test predefinito che può essere usato per gestire le richieste HTTP senza sovraccarico di rete, ovvero è possibile eseguire tali test più velocemente rispetto all'uso di un host Web reale. L'host Web di test (TestServer) è disponibile in un componente NuGet come Microsoft.AspNetCore.TestHost. Può essere aggiunto ai progetti di test di integrazione e usato per ospitare le applicazioni ASP.NET Core.
Come si può notare nel codice seguente, quando si creano test di integrazione per i controller ASP.NET Core, si crea l'istanza dei controller attraverso l'host di test. Questa funzionalità è paragonabile a una richiesta HTTP, ma viene eseguita più velocemente.
public class PrimeWebDefaultRequestShould
{
private readonly TestServer _server;
private readonly HttpClient _client;
public PrimeWebDefaultRequestShould()
{
// Arrange
_server = new TestServer(new WebHostBuilder()
.UseStartup<Startup>());
_client = _server.CreateClient();
}
[Fact]
public async Task ReturnHelloWorld()
{
// Act
var response = await _client.GetAsync("/");
response.EnsureSuccessStatusCode();
var responseString = await response.Content.ReadAsStringAsync();
// Assert
Assert.Equal("Hello World!", responseString);
}
}
Risorse aggiuntive
Steve Smith. Test dei controller (ASP.NET Core)
https://learn.microsoft.com/aspnet/core/mvc/controllers/testingSteve Smith. Test di integrazione (ASP.NET Core)
https://learn.microsoft.com/aspnet/core/test/integration-testsUnit test in .NET usando il test dotnet
https://learn.microsoft.com/dotnet/core/testing/unit-testing-with-dotnet-testxUnit.net. Sito ufficiale.
https://xunit.net/Nozioni di base su Unit Test.
https://learn.microsoft.com/visualstudio/test/unit-test-basicsMoq. Repository GitHub.
https://github.com/moq/moqNUnit. Sito ufficiale.
https://nunit.org/
Implementazione di test di servizio in un'applicazione a più contenitori
Come indicato in precedenza, quando si testano applicazioni a più contenitori, tutti i microservizi devono essere eseguiti all'interno dell'host Docker o del cluster di contenitori. I test di servizio end-to-end che includono più operazioni che implicano diversi microservizi richiedono la distribuzione e l'avvio dell'intera applicazione nell'host Docker con l'esecuzione del comando docker-compose up (o un meccanismo analogo, se si usa un agente di orchestrazione). Quando l'intera applicazione e tutti i relativi servizi saranno in esecuzione, sarà possibile eseguire i test funzionali e di integrazione end-to-end.
Sono possibili alcuni approcci. Nel file docker-compose.yml usato per distribuire l'applicazione, a livello di soluzione è possibile espandere il punto di ingresso e usare dotnet test. È anche possibile usare un altro file Compose che esegua i test nell'immagine specificata come destinazione. Se per i test di integrazione si usa un altro file Compose che include i microservizi e i database nei contenitori, ci si assicura di ripristinare sempre lo stato originale dei dati correlati prima di eseguire i test.
Quando l'applicazione Compose è operativa, è possibile sfruttare i punti di interruzione e le eccezioni se si esegue Visual Studio. In alternativa, è possibile eseguire i test di integrazione nella pipeline CI in Azure DevOps Services o qualsiasi altro sistema di integrazione continua/recapito continuo che supporti i contenitori Docker.
Test in eShopOnContainers
I test dell'applicazione di riferimento (eShopOnContainers) sono stati ristrutturati di recente. Sono ora disponibili quattro categorie:
Unit test, normali, presenti all'interno dei progetti {MicroserviceName}.UnitTests
Test funzionali o di integrazione dei microservizi, con test case che coinvolgono l'infrastruttura di ogni microservizio, ma sono isolati dagli altri e sono presenti all'interno dei progetti {MicroserviceName}.FunctionalTests.
Test funzionali/di integrazione delle applicazioni, incentrati sull'integrazione dei microservizi, con test case che esercitano diversi microservizi. Questi test si trovano nel progetto Application.FunctionalTests.
Sebbene gli unit e i test di integrazione siano organizzati in una cartella di test all'interno del progetto di microservizio, i test di applicazione e carico vengono gestiti separatamente nella cartella radice, come illustrato nella figura 6-25.
Figura 6-25. Struttura delle cartelle di test in eShopOnContainers
I test funzionali/di integrazione di microservizi e applicazioni vengono eseguiti da Visual Studio, usando il normale runner dei test, ma prima è necessario avviare i servizi di infrastruttura necessari, con un set di file docker-compose contenuti nella cartella di test della soluzione:
docker-compose-test.yml
version: '3.4'
services:
redis.data:
image: redis:alpine
rabbitmq:
image: rabbitmq:3-management-alpine
sqldata:
image: mcr.microsoft.com/mssql/server:2017-latest
nosqldata:
image: mongo
docker-compose-test.override.yml
version: '3.4'
services:
redis.data:
ports:
- "6379:6379"
rabbitmq:
ports:
- "15672:15672"
- "5672:5672"
sqldata:
environment:
- SA_PASSWORD=Pass@word
- ACCEPT_EULA=Y
ports:
- "5433:1433"
nosqldata:
ports:
- "27017:27017"
Per eseguire i test funzionali e di integrazione, è pertanto necessario eseguire prima questo comando dalla cartella dei test della soluzione:
docker-compose -f docker-compose-test.yml -f docker-compose-test.override.yml up
Come si può notare, questi file docker-compose avviano solo i microservizi Redis, RabbitMQ, SQL Server e MongoDB.
Risorse aggiuntive
Unità & Test di integrazione nei eShopOnContainers
https://github.com/dotnet-architecture/eShopOnContainers/wiki/Unit-and-integration-testingTest di carico nei eShopOnContainers
https://github.com/dotnet-architecture/eShopOnContainers/wiki/Load-testing