In questo script estendiamo i concetti che abbiamo visto nell'articolo su Docker e ASP.NET Core (https://www.aspitalia.com/articoli/asp.net-core/docker-aspnet-core-portare-nostri-siti-web-container.aspx) per capire come i container, una tecnologia così pratica e di semplice utilizzo, possano essere impiegati in attività quotidiane come il test delle applicazioni.
Nei precedenti script (https://www.aspitalia.com/script/1272/Unit-Testing-ASP.NET-Core.aspx e https://www.aspitalia.com/script/1273/Testare-Risposta-Action-ASP.NET-Core.aspx) abbiamo visto come lo unit testing ci aiuti ad esercitare i nostri componenti in maniera individuale (da qui la parola "unit") così da identificare i bug precocemente.
Limitarsi allo unit testing, tuttavia, risolve solo una parte del problema. Infatti, per assicurarci che il nostro codice stia effettivamente funzionando secondo la specifica, dobbiamo anche verificare che l'interazione tra i componenti sia corretta, soprattutto nei confronti di altri servizi infrastrutturali come il database e i webservice, che potrebbero essere soggetti a disservizi temporanei.
Con l'integration testing andiamo quindi a ricreare una situazione che sia quanto più simile a quella che avremmo nel server di produzione, allo scopo di esaminare il comportamento dell'applicazione nel suo scenario reale di funzionamento. Questa pratica ci permette di dare risposta a domande come le seguenti:
- La mia applicazione funzionerà bene anche su server Linux, nonostante io l'abbia sviluppata su Windows (o viceversa)?
- Risulterà reattiva anche con molti dati nel database e con parecchi utenti che la usano in contemporanea?
- Sarà in grado di tollerare disconnessioni durante la comunicazione con un webservice o un database?
Grazie a Docker, possiamo predisporre un ambiente di test con il minimo lo sforzo, dato che richiede una configurazione minimale e semplici comandi. Questo è possibile grazie anche a Microsoft, che ha messo a disposizione (tra le altre) due immagini Docker per ASP.NET Core (microsoft/aspnetcore e microsoft/aspnetcore-build) e una per SQL Server (microsoft/mssql-server-linux), tutte basate su Linux.
Lo scenario che andremo a ricreare è quello di una normale applicazione ASP.NET Core Web API che usa un database SQL Server per la persistenza dei dati. Un progetto di integration test invierà richieste all'applicazione e ne verificherà le risposte. Per realizzarlo, configureremo i tre container illustrati nell'immagine.
Iniziamo installando la Docker Community Edition che possiamo ottenere gratuitamente dalla sua pagina di download: https://docs.docker.com/engine/installation/ . Per i sistemi Windows, è necessario disporre di Windows 10 Pro/Enterprise o Windows Server 2016, con virtualizzazione e Hyper-V entrambi abilitati.
Terminata l'installazione, riprendiamo lo sviluppo dell'applicazione che abbiamo creato nei precedenti script e che ora è disponibile anche su GitHub, al seguente indirizzo: https://github.com/BrightSoul/AspNetCoreIntegrationTestingWithDockerCompose .
Aggiungiamo un progetto dedicato all'integration testing e, in esso, definiamo il seguente test che usa la classe HttpClient per inviare vere e proprie richieste HTTP alla Web API.
private const string webApiBaseUrl = "http://webapi/api"; [TestMethod] public async Task ShouldRetrieveValuesThatWereInsertedBefore() { using (var client = new HttpClient()) { var expectedValue = "aspitalia"; var valuesEndpoint = $"{webApiBaseUrl}/values"; //Inviamo vere e proprie richieste HTTP per inserire un valore e recuperare l'elenco dei valori esistenti await client.PostAsync( valuesEndpoint, new StringContent(JsonConvert.SerializeObject(expectedValue), Encoding.UTF8, "application/json")); var response = await client.GetStringAsync(valuesEndpoint); var values = JsonConvert.DeserializeObject(response) as JArray; Assert.IsNotNull(values); Assert.AreEqual(1, values.Count); Assert.AreEqual(expectedValue, values[0].Value<string>("value")); } }
Così com'è, il test non potrà avere successo. Infatti, nel momento in cui lo eseguiamo con il comando dotnet test, l'applicazione ASP.NET Core Web API non sarà in esecuzione e il nome host "webapi" non sarà certamente raggiungibile.
Facciamo in modo che sia Docker stesso, per mezzo del suo tool Docker Compose, ad orchestrare la preparazione e l'avvio dei container necessari. Prepariamo quindi il file di configurazione docker-compose.yml nella directory principale della soluzione e definiamo i tre container test, webapi e db.
In particolare notiamo come test dipenda da webapi, che potrà essere raggiunto sulla porta 80 con il nome host "webapi". A sua volta, webapi dipende da db, che espone la porta 1433 agli altri container.
version: '3' services: test: image: test build: context: ./tests/integration dockerfile: Dockerfile depends_on: - webapi webapi: image: webapi build: context: ./src dockerfile: Dockerfile expose: - "80" depends_on: - db db: image: microsoft/mssql-server-linux environment: ACCEPT_EULA: Y SA_PASSWORD: Password1 expose: - "1433"
Nel progetto ASP.NET Core Web API creiamo un Dockerfile in cui inseriamo i comandi specifici per compilare e lanciare l'applicazione.
# Passo 1: ripristiniamo i pacchetti NuGet e compiliamo il sorgente FROM microsoft/aspnetcore-build AS builder WORKDIR /source COPY MyWebApiApp.csproj . RUN dotnet restore COPY . . RUN dotnet publish --output /app/ --configuration Release # Passo 2: eseguiamo l'applicazione FROM microsoft/aspnetcore WORKDIR /app COPY --from=builder /app . ENV ASPNETCORE_ENVIRONMENT Production ENTRYPOINT ["dotnet", "MyWebApiApp.dll"]
Mentre, nel progetto di integration testing, creiamo il seguente Dockerfile.
FROM microsoft/aspnetcore-build WORKDIR /app COPY . . RUN dotnet restore ENTRYPOINT ["dotnet", "test"]
La configurazione è completa: non resta che lanciare il comando docker-compose per preparare le immagini ed avviare i relativi container.
docker-compose -f docker-compose.yml build &&^ docker-compose -f docker-compose.yml up --force-recreate --abort-on-container-exit
Con il flag --abort-on-container-exit ci assicuriamo che i container vengano tutti terminati alla conclusione dei test. Inoltre, è importante che i container vengano ricreati ad ogni esecuzione grazie a --force-recreate, in modo che i test possano produrre risultati deterministici.
Al primo avvio, il comando docker-compose richiederà qualche minuto per scaricare le immagini dal Docker Hub (circa 2GB). Subito dopo vedremo apparire le voci di log dai 3 container, tra cui la conferma che i test sono stati eseguiti correttamente.
In pochissimo tempo siamo riusciti a testare la nostra applicazione su sistema Linux senza dover necessariamente installare il sistema operativo in una macchina virtuale. Inoltre, grazie a Docker, abbiamo potuto configurare un ambiente di testing estremamente portabile, eseguibile anche dai nostri collaboratori o da un eventuale server di Continuous Integration, che potrà lanciare i test periodicamente o dopo ogni commit.
Commenti
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.
Approfondimenti
Popolare una classe a partire dal testo, con Semantic Kernel e ASP.NET Core Web API
Garantire la provenienza e l'integrità degli artefatti prodotti su GitHub
Disabilitare automaticamente un workflow di GitHub
Gestire liste di tipi semplici con Entity Framework Core
Inference di dati strutturati da testo con Semantic Kernel e ASP.NET Core Web API
Utilizzare un numero per gestire la concorrenza ottimistica con SQL Server ed Entity Framework
Gestione degli stili CSS con le regole @layer
Potenziare Azure AI Search con la ricerca vettoriale
Utilizzare gRPC su App Service di Azure
Esporre i propri servizi applicativi con Semantic Kernel e ASP.NET Web API
Change tracking e composition in Entity Framework
Implementare l'infinite scroll con QuickGrid in Blazor Server