Übung: Hinzufügen von Komponententests zur Anwendung

Abgeschlossen

In dieser Lektion fügen wir Komponententests zum automatisierten Build hinzu, den wir mit Microsoft Azure-Pipelines erstellt haben. In den Code Ihres Teams haben sich Regressionsfehler eingeschlichen, die zu Fehlern in der Filterfunktion der Bestenliste führen. Insbesondere wird der Spielmodus falsch angezeigt.

Das folgende Bild zeigt das Problem. Wählt der Benutzer „Milky Way“ aus, um nur die Punktestände dieser Karte anzuzeigen, werden stattdessen auch Ergebnisse von anderen Karten, wie z. B. Andromeda, angezeigt.

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

Das Team möchte den Fehler abfangen, bevor er die Tester erreicht. Komponententests eignen sich hervorragend, um automatische Tests auf Regressionsfehler auszuführen.

Das Hinzufügen der Komponententests an diesem Punkt im Prozess führt dazu, dass das Team einen Vorsprung erhält, während sie die Space Game-Web-App verbessern. Die Anwendung verwendet zum Speichern von Höchstpunkteständen und Spielerprofilen eine Dokumentdatenbank. Aktuell werden dafür lokale Testdaten verwendet. Später soll die App mit einer Livedatenbank verbunden werden.

Für C#-Anwendungen stehen viele Komponententest-Frameworks zur Verfügung. Wir verwenden NUnit, da es in der Community beliebt ist.

Hier sehen Sie den Komponententest, mit dem Sie arbeiten:

[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));
}

Sie können die Bestenliste nach einer beliebigen Kombination aus Spieltyp und Spielkarte filtern.

Mit diesem Test wird die Bestenliste nach den Höchstpunkteständen abgefragt, und es wird überprüft, ob jedes Ergebnis mit der angegebenen Spielkarte übereinstimmt.

In einer NUnit-Testmethode stellt TestCase Inlinedaten zum Testen dieser Methode bereit. Hier ruft NUnit die Komponententestmethode FetchOnlyRequestedGameRegion folgendermaßen auf:

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

Beachten Sie den Aufruf der Methode Assert.That am Ende des Tests. Eine Assertion ist eine Bedingung oder Anweisung, die Sie als TRUE deklarieren. Stellt sich die Bedingung als FALSE heraus, könnte das auf einen Fehler im Code hinweisen. NUnit führt mit den von Ihnen angegebenen Inlinedaten jede Testmethode aus und erfasst das jeweilige Ergebnis als bestandenen oder fehlerhaften Test.

Viele Komponententest-Frameworks bieten Überprüfungsmethoden mit nahezu natürlicher Sprache. Diese Methoden erleichtern das Lesen der Tests und das Zuordnen der Tests zu den Anforderungen der Anwendung.

Nehmen Sie z. B. die Assert-Anweisung in diesem Beispiel:

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

Diese Zeile kann etwa folgendermaßen gelesen werden:

Es soll bestätigt werden, dass die Spielregion jedes zurückgegebenen Punktestands mit der angegebenen Spielregion übereinstimmt.

Führen Sie die folgenden Schritte aus:

  1. Rufen Sie einen Branch aus dem GitHub-Repository ab, das die Komponententests enthält.
  2. Führen Sie die Tests lokal aus, um sicherzustellen, dass sie erfolgreich sind.
  3. Fügen Sie Ihrer Pipelinekonfiguration Aufgaben hinzu, um die Tests auszuführen und die Ergebnisse zu erfassen.
  4. Übertragen Sie den Branch per Push in Ihr GitHub-Repository.
  5. Ihr Azure Pipelines-Projekt erstellt nun automatisch die Anwendung und führt die Tests aus.

Abrufen des Branchs aus GitHub

In diesem Abschnitt rufen Sie den Branch unit-tests aus GitHub ab und checken ihn aus oder wechseln zu ihm.

Dieser Branch enthält das Space Game-Projekt, mit dem Sie in den vorherigen Modulen gearbeitet haben, sowie eine anfängliche Azure Pipelines-Konfiguration.

  1. Öffnen Sie in Visual Studio Code das integrierte Terminal.

  2. Führen Sie die folgenden git-Befehle aus, um einen Branch namens unit-tests aus dem Microsoft-Repository abzurufen, und wechseln Sie zu diesem Branch.

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

    Mit dem Format dieses Befehls können Sie Startercode aus dem GitHub-Repository von Microsoft (upstream) abrufen. Sie pushen diesen Branch in Ihr GitHub-Repository (origin).

  3. Optional können Sie die Datei azure-pipelines.yml in Visual Studio Code öffnen und sich mit der anfänglichen Konfiguration vertraut machen. Die Konfiguration ähnelt der Basiskonfiguration, die Sie im Modul Create a build pipeline with Azure Pipelines (Erstellen einer Buildpipeline mit Azure Pipelines) erstellt haben. Damit wird nur die Releasekonfiguration der Anwendung erstellt.

Lokales Ausführen der Tests

Bevor Sie Tests an die Pipeline übermitteln, sollten Sie diese lokal ausführen. Das machen Sie hier.

  1. Öffnen Sie in Visual Studio Code das integrierte Terminal.

  2. Führen Sie dotnet build aus, um alle Projekte in der Projektmappe zu erstellen.

    dotnet build --configuration Release
    
  3. Führen Sie den folgenden dotnet test-Befehl aus, um die Komponententests auszuführen:

    dotnet test --configuration Release --no-build
    

    Mit dem Flag --no-build wird angegeben, dass das Projekt vor der Ausführung nicht erstellt werden soll. Sie müssen das Projekt nicht mehr erstellen, da Sie dies bereits im vorherigen Schritt getan haben.

    Sie sollten sehen, dass alle fünf Tests bestehen.

    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
    

    Bei diesem Beispiel dauerte die Testausführung weniger als eine Sekunde.

    Beachten Sie, dass fünf vollständige Testläufe ausgeführt wurden. Obwohl mit FetchOnlyRequestedGameRegion nur eine Testmethode angegeben wurde, wurde dieser Test fünfmal ausgeführt – wie in den TestCase-Inlinedaten angegeben einmal für jede Spielkarte.

  4. Führen Sie die Tests ein weiteres Mal aus. Geben Sie jedoch dieses Mal die Option --logger an, um die Ergebnisse in eine Protokolldatei zu schreiben.

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

    Sie sehen in der Ausgabe, dass im Verzeichnis TestResults eine TRX-Datei erstellt wurde.

    Eine TRX-Datei ist ein XML-Dokument, das die Ergebnisse eines Testlaufs enthält. Das Format wird häufig für Testergebnisse verwendet, da mit Visual Studio und anderen Tools die Ergebnisse visualisiert werden können.

    Später erfahren Sie, wie Sie mit Azure Pipelines Ihre Testergebnisse beim Durchlaufen der Pipeline visualisieren und nachverfolgen können.

    Hinweis

    TRX-Dateien sollen nicht in die Quellcodeverwaltung aufgenommen werden. Mit einer GITIGNORE-Datei können Sie angeben, welche temporären und anderen Dateien von Git ignoriert werden sollen. Die GITIGNORE-Datei des Projekts wurde bereits so eingerichtet, dass jeglicher Inhalt des Verzeichnisses TestResults ignoriert wird.

  5. Als optionalen Schritt können Sie in Visual Studio Code im Ordner Tailspin.SpaceGame.Web.Tests die Datei DocumentDBRepository_GetItemsAsyncShould.cs öffnen und den Testcode überprüfen. Selbst wenn Sie nicht besonders an der Erstellung von .NET-Apps interessiert sind, kann der Testcode nützlich sein, da er dem Code in anderen Frameworks für Komponententests ähnelt.

Hinzufügen von Aufgaben zur Pipelinekonfiguration

In diesem Abschnitt konfigurieren Sie die Buildpipeline so, dass Ihre Komponententests ausgeführt und die Ergebnisse gesammelt werden.

  1. Ändern Sie in Visual Studio Code azure-pipelines.yml wie folgt:

    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 dieser Version wird die Buildaufgabe DotNetCoreCLI@2 eingeführt.

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

    Mit dieser Buildaufgabe wird der dotnet test-Befehl ausgeführt.

    Beachten Sie, dass mit dieser Aufgabe das Argument --logger trx nicht angegeben wird, das Sie beim manuellen Ausführen der Tests verwendet haben. Es wird stattdessen vom Argument publishTestResults hinzugefügt. Dieses Argument gibt an, dass die Pipeline die TRX-Datei in einem temporären Verzeichnis generieren soll, auf das über die integrierte Variable $(Agent.TempDirectory) zugegriffen werden kann. Zudem werden die Ergebnisse der Aufgabe in der Pipeline veröffentlicht.

    Das projects-Argument gibt alle C#-Projekte an, die „**/*.Tests.csproj“ entsprechen. Der Teil „**“ entspricht allen Verzeichnissen und der Teil „*. Tests.csproj“ stimmt mit allen Projekten überein, deren Dateiname mit „.Tests.csproj“ endet. Der unit-tests-Branch enthält nur ein Komponententestprojekt, Tailspin.SpaceGame.Web.Tests.csproj. Durch Angabe eines Musters können Sie jedoch weitere Testprojekte ausführen, ohne Ihre Buildkonfiguration zu ändern.

Übertragen des Branchs per Push an GitHub

In diesem Abschnitt übertragen Sie Ihre Änderungen per Push an GitHub. Außerdem wird die Pipeline ausgeführt. Denken Sie daran, dass Sie sich derzeit im Branch unit-tests befinden.

  1. Fügen Sie azure-pipelines.yml im integrierten Terminal zum Index hinzu, committen Sie die Änderungen, und pushen Sie den Branch an GitHub.

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

Beobachten der Testausführung in Azure Pipelines

Hier wird gezeigt, wie die Tests in der Pipeline ausgeführt und die Ergebnisse dann in Microsoft Azure Test Plans visualisiert werden. Azure Test Plans alle Tools bereit, die zum erfolgreichen Testen Ihrer Anwendungen benötigt werden. Sie können manuelle Testpläne erstellen und ausführen, automatisierte Tests generieren und Feedback von den Projektbeteiligten erfassen.

  1. Verfolgen Sie die einzelnen Schritte des Buildvorgangs in Azure Pipelines.

    Sie sehen, dass die Aufgabe Run unit tests - Release die Komponententests genauso ausführt wie manuell über die Befehlszeile.

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

  2. Navigieren Sie zurück zur Pipelineübersicht.

  3. Wechseln Sie zur Registerkarte Tests.

    Sie sehen eine Zusammenfassung des Testlaufs. Alle fünf Tests wurden bestanden.

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

  4. Wählen Sie in Azure DevOps Test Plans und dann Runs aus.

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

    Die letzten Testläufe, einschließlich des soeben ausgeführten Testlaufs, werden angezeigt.

  5. Doppelklicken Sie auf den letzten Testlauf.

    Daraufhin wird eine Zusammenfassung der Ergebnisse angezeigt.

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

    In diesem Beispiel wurden alle fünf Tests bestanden. Wenn ein Test fehlgeschlagen ist, können Sie zur Buildaufgabe navigieren, um weitere Details zu erfahren.

    Sie können auch die TRX-Datei herunterladen, um sie in Visual Studio oder einem anderem Visualisierungstool zu untersuchen.

Obwohl Sie nur einen Test hinzugefügt haben, ist dieser ein guter Anfang, da das unmittelbare Problem behoben wird. Nun hat das Team eine Basis, von der aus weitere Tests hinzugefügt und ausgeführt werden können, wenn der Prozess erweitert wird.

Mergen des Branchs in den Master

In einem realen Szenario, wenn Sie mit den Ergebnissen zufrieden waren, können Sie die unit-tests Verzweigung mit mainzusammenführen, aber aus Platzgründen überspringen wir diesen Prozess für jetzt.