Ejercicio: adición de pruebas unitarias a la aplicación

Completado

En esta unidad, agregaremos pruebas unitarias a la compilación automatizada que creamos con Microsoft Azure Pipelines. Los errores de regresión están pasando al código del equipo y están interrumpiendo la funcionalidad de filtrado de la tabla de clasificación. Más concretamente, sigue apareciendo el modo de juego incorrecto.

En la imagen siguiente se muestra el problema. Cuando un usuario selecciona "Vía láctea" para mostrar solo las puntuaciones de ese mapa de juego, obtiene resultados de otros mapas de juego, como Andrómeda.

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

El equipo quiere detectar el error antes de que llegue a los evaluadores. Las pruebas unitarias son una excelente manera de probar automáticamente los errores de regresión.

Agregar las pruebas unitarias en este momento del proceso dará al equipo una ventaja inicial a medida que se mejora la aplicación web de Space Game. La aplicación usa una base de datos de documentos para almacenar las puntuaciones más altas y los perfiles de los jugadores. En este momento, usa datos de prueba locales, pero planean conectar más adelante la aplicación a una base de datos activa.

Hay muchos marcos de trabajo de pruebas unitarias disponibles para aplicaciones de C#. Se usa NUnit porque es popular con la comunidad.

Esta es la prueba unitaria con la que trabaja:

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

Puede filtrar la tabla de clasificación por cualquier combinación de tipo y mapa de juego.

Esta prueba busca en la tabla de clasificación las puntuaciones más altas y comprueba que cada resultado coincida con el mapa de juego proporcionado.

En un método de prueba NUnit, TestCase proporciona datos insertados que se usan para probar ese método. En este caso, NUnit llama al método de prueba unitaria FetchOnlyRequestedGameRegion de la siguiente manera:

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

Observe la llamada al método Assert.That al final de la prueba. Una aserción es una condición o instrucción que se declara como verdadera. Si la condición resulta ser false, esto podría indicar un error en el código. NUnit ejecuta cada método de prueba con los datos insertados que ha especificado y registra el resultado como una prueba superada o con errores.

Muchos marcos de pruebas unitarias proporcionan métodos de verificación que se asemejan al lenguaje natural. Estos métodos ayudan a facilitar la lectura de las pruebas y su asignación a los requisitos de la aplicación.

Observe la aserción de este ejemplo:

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

Podría leer esta línea de la siguiente manera:

Confirme que la región de juego de cada puntuación devuelta coincide con la región de juego proporcionada.

Este es el proceso que seguirá:

  1. Capture una rama del repositorio de GitHub que contenga las pruebas unitarias.
  2. Ejecute las pruebas localmente para comprobar que se superan.
  3. Agregue tareas a la configuración de canalización para ejecutar las pruebas y recopilar los resultados.
  4. Inserte la rama en el repositorio de GitHub.
  5. Vea cómo el proyecto de Azure Pipelines compila automáticamente la aplicación y ejecuta las pruebas.

Captura de la rama de GitHub

Aquí se capturará la rama unit-tests de GitHub y se modificará o cambiará a esa rama.

Esta rama contiene el proyecto Space Game con el que ha trabajado en los módulos anteriores y una configuración de Azure Pipelines con la que empezar.

  1. En Visual Studio Code, abra el terminal integrado.

  2. Ejecute los comandos git siguientes para capturar una rama denominada unit-tests desde el repositorio de Microsoft y, después, cambie a esa rama.

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

    El formato de este comando le permite obtener el código de inicio del repositorio de GitHub de Microsoft, conocido como upstream. En resumen, insertará esta rama en el repositorio de GitHub, conocido como origin.

  3. Como paso opcional, abra el archivo azure-pipelines.yml desde Visual Studio Code y familiarícese con la configuración inicial. La configuración es similar a la básica creada en el módulo Crear una canalización de compilación con Azure Pipelines. Solo compila la configuración de versión de la aplicación.

Ejecución de las pruebas localmente

Se recomienda ejecutar todas las pruebas localmente antes de enviarlas a la canalización. Es lo que hará aquí.

  1. En Visual Studio Code, abra el terminal integrado.

  2. Ejecute dotnet build para compilar cada proyecto en la solución.

    dotnet build --configuration Release
    
  3. Ejecute el siguiente comando dotnet test para realizar las pruebas unitarias:

    dotnet test --configuration Release --no-build
    

    La marca --no-build especifica que no se compile el proyecto antes de ejecutarlo. No hace falta que compile el proyecto porque ya lo hizo en el paso anterior.

    Debería ver que se superan las cinco pruebas.

    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
    

    En este ejemplo, las pruebas tardaron menos de un segundo en ejecutarse.

    Observe que se realizaron cinco pruebas en total. Aunque solo se definió un método de prueba (FetchOnlyRequestedGameRegion), esta prueba se ejecuta cinco veces, una vez para cada mapa de juego, según lo especificado en los datos insertados TestCase.

  4. Ejecute las pruebas una segunda vez. Esta vez, proporcione la opción --logger para escribir los resultados en un archivo de registro.

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

    Verá en la salida que se habrá creado un archivo TRX en el directorio TestResults.

    Un archivo TRX es un documento XML que contiene los resultados de una serie de pruebas. Es un formato popular para los resultados de las pruebas porque Visual Studio y otras herramientas pueden ayudarle a visualizar los resultados.

    Más adelante, verá cómo Azure Pipelines puede ayudarle a visualizar y seguir los resultados de las pruebas mientras se ejecutan en la canalización.

    Nota:

    Los archivos TRX no están diseñados para incluirse en el control de código fuente. Un archivo .gitignore permite especificar los archivos temporales y de otro tipo que quiere que Git ignore. El archivo .gitignore del proyecto ya está configurado para ignorar todo el contenido del directorio TestResults.

  5. Como paso opcional, abra el archivo DocumentDBRepository_GetItemsAsyncShould.cs desde la carpeta Tailspin.SpaceGame.Web.Tests en Visual Studio Code y examine el código de prueba. Incluso aunque no le interese compilar aplicaciones de .NET de forma específica, el código de prueba le resultará útil porque se parece al código que se ve en otros marcos de trabajo de pruebas unitarias.

Adición de tareas a la configuración de canalización

Aquí configurará la canalización de compilación para ejecutar las pruebas unitarias y recopilar los resultados.

  1. En Visual Studio Code, modifique azure-pipelines.yml de la manera siguiente:

    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()
    

    Esta versión introduce la tarea de compilación DotNetCoreCLI@2.

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

    Esta tarea de compilación ejecuta el comando dotnet test.

    Observe que esta tarea no especifica el argumento --logger trx que usó al ejecutar las pruebas manualmente. El argumento publishTestResults lo agrega automáticamente. Este argumento le indica a la canalización que genere el archivo TRX en un directorio temporal, accesible a través de la variable integrada $(Agent.TempDirectory). Además, publica los resultados de la tarea en la canalización.

    El argumento projects especifica todos los proyectos de C# que coinciden con "**/*.Tests.csproj". La parte "**" coincide con todos los directorios y la parte "*.Tests.csproj" coincide con todos los proyectos cuyo nombre de archivo termine con ".Tests.csproj". La rama unit-tests contiene solo un proyecto de prueba unitaria, Tailspin.SpaceGame.Web.Tests.csproj. Si se especifica un patrón, se pueden ejecutar más proyectos de prueba adicionales sin necesidad de modificar la configuración de compilación.

Inserción de la rama en GitHub

Aquí podrá insertar los cambios en GitHub y ver la ejecución de la canalización. Recuerde que actualmente está en la rama unit-tests.

  1. En el terminal integrado, agregue azure-pipelines.yml al índice, confirme los cambios e inserte la rama en GitHub.

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

Vea cómo Azure Pipelines ejecuta las pruebas

Aquí verá la ejecución de las pruebas en la canalización y, después, visualizará los resultados desde Microsoft Azure Test Plans. Azure Test Plans proporciona todas las herramientas que necesita para probar correctamente las aplicaciones. Puede crear y ejecutar planes de pruebas manuales, generar pruebas automatizadas y recopilar comentarios de las partes interesadas.

  1. En Azure Pipelines, realice el seguimiento de la compilación a lo largo de cada uno de los pasos.

    Verá que la tarea Run unit tests - Release (Ejecutar pruebas unitarias: versión) ejecuta las pruebas unitarias tal como lo hizo manualmente desde la línea de comandos.

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

  2. Vuelva al resumen de la canalización.

  3. Vaya a la pestaña Pruebas.

    Verá un resumen de la serie de pruebas. Se han superado las cinco pruebas.

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

  4. En Azure DevOps, seleccione Test Plans y, después, Runs (Ejecuciones).

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

    Verá las series de pruebas más recientes, incluida la que acaba de ejecutar.

  5. Haga doble clic en la serie de pruebas más reciente.

    Verá un resumen de los resultados.

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

    En este ejemplo, se han superado las cinco pruebas. Si se han producido errores en algunas de las pruebas, puede ir a la tarea de compilación para obtener más información.

    También puede descargar el archivo TRX para examinarlo con Visual Studio u otra herramienta de visualización.

Aunque solo agregó una prueba, supone un buen punto de partida y se corrige el problema inmediato. Ahora, el equipo tiene un lugar en el que agregar más pruebas y ejecutarlas a medida que mejoran el proceso.

Fusión de la rama (“branch”) con la principal mediante combinación con “merge”

En un escenario real, si estuviera satisfecho con los resultados podría combinar la rama unit-tests con main, pero para una mayor brevedad se omitirá ese proceso por ahora.