Esercizio - Aggiungere unit test all'applicazione

Completato

In questa unità, verranno aggiunti unit test alla build automatizzata creata con Microsoft Azure Pipelines. I bug di regressione si insinuano nel codice del team e interrompono la funzionalità di filtro del tabellone punteggi. In particolare, continua ad apparire la modalità di gioco errata.

L'immagine seguente illustra il problema. Quando un utente seleziona "Via Lattea" per mostrare solo i punteggi di quella mappa di gioco, ottiene i risultati di altre mappe di gioco, ad esempio Andromeda.

A screenshot of the leaderboard showing incorrect results: Andromeda galaxy scores show in the Milky Way galaxy listing.

Il team vuole individuare l'errore prima che raggiunga i tester. Gli unit test sono ideali per automatizzare i test dei bug di regressione.

L'aggiunta degli unit test a questo punto del processo offrirà al team un vantaggio per migliorare l'app web Space Game.. L'applicazione usa un database di documenti per archiviare i punteggi alti e i profili dei giocatori. Al momento, usa dati di test locali. In seguito, è previsto che l'app venga connessa a un database live.

Per le applicazioni C# sono disponibili numerosi framework di unit test. Si userà NUnit perché è molto diffuso nella community.

Ecco lo unit test con cui si sta lavorando:

[TestCase("Milky Way")]
[TestCase("Andromeda")]
[TestCase("Pinwheel")]
[TestCase("NGC 1300")]
[TestCase("Messier 82")]
public void FetchOnlyRequestedGameRegion(string gameRegion)
{
    const int PAGE = 0; // take the first page of results
    const int MAX_RESULTS = 10; // sample up to 10 results

    // Form the query predicate.
    // This expression selects all scores for the provided game region.
    Expression<Func<Score, bool>> queryPredicate = score => (score.GameRegion == gameRegion);

    // Fetch the scores.
    Task<IEnumerable<Score>> scoresTask = _scoreRepository.GetItemsAsync(
        queryPredicate, // the predicate defined above
        score => 1, // we don't care about the order
        PAGE,
        MAX_RESULTS
    );
    IEnumerable<Score> scores = scoresTask.Result;

    // Verify that each score's game region matches the provided game region.
    Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));
}

È possibile filtrare la classifica in base a qualsiasi combinazione di tipo e mappa del gioco.

Il test esegue una query sulla classifica per trovare i punteggi alti e verifica che ogni risultato corrisponda alla mappa del gioco specificata.

Nel metodo di test NUnit TestCase fornisce i dati inline da usare per testare questo metodo. In questo caso, NUnit chiama il metodo di unit test FetchOnlyRequestedGameRegion come indicato di seguito:

FetchOnlyRequestedGameRegion("Milky Way");
FetchOnlyRequestedGameRegion("Andromeda");
FetchOnlyRequestedGameRegion("Pinwheel");
FetchOnlyRequestedGameRegion("NGC 1300");
FetchOnlyRequestedGameRegion("Messier 82");

Si noti la chiamata al metodo Assert.That alla fine del test. Una asserzione è una condizione o un'istruzione che si dichiara vera. Se la condizione risulta falsa, potrebbe indicare un bug nel codice. NUnit esegue ogni metodo del test usando i dati inline specificati e registra i risultati come test superati o non superati.

Molti framework di unit test includono metodi di verifica simili al linguaggio naturale. Questi metodi consentono di rendere i test facili da leggere e di eseguire il mapping dei test sui requisiti dell'applicazione.

Si consideri l'affermazione fatta in questo esempio:

Assert.That(scores, Is.All.Matches<Score>(score => score.GameRegion == gameRegion));

La riga si potrebbe leggere come:

Dichiarare che l'area di gioco di ogni punteggio restituito corrisponde all'area di gioco specificata.

Ecco il processo da seguire:

  1. Recuperare un ramo dal repository GitHub che contiene gli unit test.
  2. Eseguire i test in locale per verificare che vengano superati.
  3. Aggiungere attività alla configurazione della pipeline per eseguire i test e raccogliere i risultati.
  4. Eseguire il push del ramo nel repository GitHub.
  5. Osservare il progetto di Azure Pipelines che compila automaticamente l'applicazione ed esegue i test.

Recuperare il ramo da GitHub

In questo caso, si recupera il ramo unit-tests da GitHub e si estrae o si passa a tale ramo.

Questo ramo contiene il progetto Space Game usato nei moduli precedenti e una configurazione di Azure Pipelines con cui iniziare.

  1. Aprire il terminale integrato in Visual Studio Code.

  2. Eseguire i comandi git seguenti per recuperare un ramo denominato unit-tests dal repository di Microsoft e passare a tale ramo.

    git fetch upstream unit-tests
    git checkout -B unit-tests upstream/unit-tests
    

    Il formato di questo comando consente di ottenere il codice di avvio dal repository GitHub di Microsoft, noto come upstream. A breve, si eseguirà il push del ramo nel repository GitHub, noto come origin.

  3. Come passaggio facoltativo, aprire il file azure-pipelines.yml in Visual Studio Code e acquisire familiarità con la configurazione iniziale. La configurazione è simile a quella di base creata nel modulo Creare una pipeline di compilazione con Azure Pipelines. Compila solo la configurazione Release dell'applicazione.

Eseguire i test in locale

È consigliabile eseguire tutti i test in locale prima di inviarli alla pipeline. Questa operazione verrà eseguita ora.

  1. Aprire il terminale integrato in Visual Studio Code.

  2. Eseguire dotnet build per compilare ogni progetto della soluzione.

    dotnet build --configuration Release
    
  3. Eseguire il comando dotnet test seguente per eseguire gli unit test:

    dotnet test --configuration Release --no-build
    

    Il flag --no-build specifica di non compilare il progetto prima di eseguirlo. Non è necessario compilare il progetto perché è stato compilato nel passaggio precedente.

    Si noterà che tutti e cinque i test vengono superati.

    Starting test execution, please wait...
    A total of 1 test files matched the specified pattern.
    
    Passed!  - Failed:     0, Passed:     5, Skipped:     0, Total:     5, Duration: 57 ms
    

    In questo esempio l'esecuzione dei test ha richiesto meno di un secondo.

    Si noti che in totale ci sono cinque test. Anche se viene definito un solo metodo di test, FetchOnlyRequestedGameRegion, il test viene eseguito cinque volte, una per ogni mappa di gioco specificata nei dati inline TestCase.

  4. Eseguire i test una seconda volta. Questa volta, specificare l'opzione --logger per scrivere i risultati in un file di log.

    dotnet test Tailspin.SpaceGame.Web.Tests --configuration Release --no-build --logger trx
    

    L'output mostra che è stato creato un file TRX nella directory TestResults.

    Il file TRX è un documento XML che contiene i risultati dell'esecuzione dei test. È un formato popolare per i risultati dei test perché Visual Studio e altri strumenti consentono di visualizzare i risultati.

    In seguito, si vedrà come Azure Pipelines consente di visualizzare e tenere traccia dei risultati dei test durante l'esecuzione della pipeline.

    Nota

    I file TRX non devono essere inclusi nel controllo del codice sorgente. Un file .gitignore consente di specificare quali file temporanei e di altro tipo dovranno essere ignorati da Git. Il file gitignore del progetto è già configurato per ignorare qualsiasi contenuto della directory TestResults.

  5. Come passaggio facoltativo, aprire il file DocumentDBRepository_GetItemsAsyncShould.cs in Visual Studio Code nella cartella Tailspin.SpaceGame.Web.Tests ed esaminare il codice di test. Anche se non si è interessati a compilare specificamente app .NET, il codice di test potrebbe risultare utile perché è simile al codice che si potrebbe trovare in altri framework di unit test.

Aggiungere attività alla configurazione della pipeline

In questo caso si configurerà la pipeline di compilazione per eseguire gli unit test e raccogliere i risultati.

  1. In Visual Studio Code modificare azure-pipelines.yml come indicato di seguito:

    trigger:
    - '*'
    
    pool:
      vmImage: 'ubuntu-20.04'
      demands:
      - npm
    
    variables:
      buildConfiguration: 'Release'
      wwwrootDir: 'Tailspin.SpaceGame.Web/wwwroot'
      dotnetSdkVersion: '6.x'
    
    steps:
    - task: UseDotNet@2
      displayName: 'Use .NET SDK $(dotnetSdkVersion)'
      inputs:
        version: '$(dotnetSdkVersion)'
    
    - task: Npm@1
      displayName: 'Run npm install'
      inputs:
        verbose: false
    
    - script: './node_modules/.bin/node-sass $(wwwrootDir) --output $(wwwrootDir)'
      displayName: 'Compile Sass assets'
    
    - task: gulp@1
      displayName: 'Run gulp tasks'
    
    - script: 'echo "$(Build.DefinitionName), $(Build.BuildId), $(Build.BuildNumber)" > buildinfo.txt'
      displayName: 'Write build info'
      workingDirectory: $(wwwrootDir)
    
    - task: DotNetCoreCLI@2
      displayName: 'Restore project dependencies'
      inputs:
        command: 'restore'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Build the project - $(buildConfiguration)'
      inputs:
        command: 'build'
        arguments: '--no-restore --configuration $(buildConfiguration)'
        projects: '**/*.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    
    - task: DotNetCoreCLI@2
      displayName: 'Publish the project - $(buildConfiguration)'
      inputs:
        command: 'publish'
        projects: '**/*.csproj'
        publishWebProjects: false
        arguments: '--no-build --configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)/$(buildConfiguration)'
        zipAfterPublish: true
    
    - task: PublishBuildArtifacts@1
      displayName: 'Publish Artifact: drop'
      condition: succeeded()
    

    In questa versione è stata introdotta l'attività di compilazione DotNetCoreCLI@2.

    - task: DotNetCoreCLI@2
      displayName: 'Run unit tests - $(buildConfiguration)'
      inputs:
        command: 'test'
        arguments: '--no-build --configuration $(buildConfiguration)'
        publishTestResults: true
        projects: '**/*.Tests.csproj'
    

    Questa attività esegue il comando dotnet test.

    Si noti che l'attività non specifica l'argomento --logger trx usato quando sono stati eseguiti i test manuali. L'argomento publishTestResults aggiunge automaticamente questa opzione. Questo argomento indica alla pipeline di generare il file TRX in una directory temporanea, accessibile tramite la variabile $(Agent.TempDirectory) predefinita. Pubblica inoltre i risultati dell'attività nella pipeline.

    L'argomento projects specifica tutti i progetti C# corrispondenti a "**/*.Test.csproj". La parte "**" corrisponde a tutte le directory e la parte "*.Tests.csproj" corrisponde a tutti i progetti il cui nome file termina con ".Tests.csproj". Il ramo unit-tests contiene un unico progetto di unit test, Tailspin.SpaceGame.Web.Tests.csproj. Specificando un modello, è possibile eseguire più progetti di test senza dover modificare la configurazione della build.

Eseguire il push del ramo in GitHub

A questo punto si eseguirà il push delle modifiche in GitHub e si vedrà l'esecuzione della pipeline. Si ricordi che attualmente ci si trova nel ramo unit-tests.

  1. Nel terminale integrato aggiungere azure-pipelines.yml all'indice, eseguire il commit delle modifiche ed eseguire il push del ramo in GitHub.

    git add azure-pipelines.yml
    git commit -m "Run and publish unit tests"
    git push origin unit-tests
    

Osservare l'esecuzione dei test in Azure Pipelines

A questo punto verranno visualizzati i test eseguiti nella pipeline e quindi i risultati di Microsoft Azure Test Plans. Azure Test Plans offre tutti gli strumenti necessari per testare correttamente le applicazioni. È possibile creare ed eseguire piani di test manuali, generare test automatizzati e raccogliere feedback dagli stakeholder.

  1. In Azure Pipelines tracciare la build in ogni passaggio.

    L'attività Esegui unit test - Rilascio esegue gli unit test che in precedenza sono stati completati manualmente dalla riga di comando.

    A screenshot of Azure Pipelines showing console output from running unit tests.

  2. Tornare al riepilogo della pipeline.

  3. Passare alla scheda Test.

    Viene visualizzato un riepilogo dell'esecuzione dei test. Tutti e cinque i test sono stati superati.

    A screenshot of Azure Pipelines showing the Tests tab with 5 total tests run and 100 percent passing.

  4. In Azure DevOps selezionare Test Plans, quindi Esecuzioni.

    A screenshot of Azure DevOps navigation menu with Test Plans section and Runs tab highlighted.

    Vengono visualizzate le esecuzioni dei test più recenti, incluso quello appena eseguito.

  5. Fare doppio clic sull'esecuzione del test più recente.

    Viene visualizzato un riepilogo dei risultati.

    A screenshot of Azure DevOps test run results summary showing 5 passed tests.

    In questo esempio tutti e cinque i test sono stati superati. Se un test non viene superato, è possibile passare all'attività di compilazione per ottenere altri dettagli.

    È anche possibile scaricare il file TRX per esaminarlo tramite Visual Studio o un altro strumento di visualizzazione.

Anche se si è aggiunto un solo test, è un buon inizio e risolve il problema immediato. Ora il team ha una risorsa in cui aggiungere altri test ed eseguirli mentre migliorano il processo.

Unire il ramo in main

In uno scenario reale, se si è soddisfatti dei risultati, è possibile unire il ramo unit-tests a main, ma per brevità questo processo verrà ignorato per ora.