.NET

13 ago, 2020

ASP.NET Core + xUnit + GitHub Actions: automatizando testes, build e deployment de Web Apps

100 visualizações
Publicidade
Artigo originalmente publicado no blog do autor, Renato Groffe
Image for post

Venho abordando o uso de GitHub Actions como solução para o build e deployment automatizado de projetos há alguns meses, com diversos artigos publicados inclusive no meu blog.

Neste novo artigo dou continuidade ao tema GitHub Actions, abordando desta vez a execução de testes automatizados durante o build e deployment de uma aplicação Web. Esta é uma necessidade bastante frequente em projetos dos mais variados tipos, haja visto a necessidade de validar alterações antes de se proceder com a publicação de uma Web App.

Detalhes da aplicação e do projeto de testes

A aplicação e o projeto de testes foram disponibilizados no seguinte repositório do GitHub:

ASP.NET Core 3.1 + REST API + xUnit + Fluent Assertions

Esta API REST permitirá a conversão de temperaturas em graus Fahrenheit para o equivalente na escala Celsius, com isto acontecendo na classe ConversorTemperaturasController:

using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;

namespace APITemperatura.Controllers
{
    [ApiController]
    [Route("[controller]")]
    public class ConversorTemperaturasController : ControllerBase
    {
        [HttpGet("Fahrenheit/{temperatura}")]
        public object Get(
            [FromServices]ILogger<ConversorTemperaturasController> logger,
            double temperatura)
        {
            logger.LogInformation(
                $"Recebida temperatura para conversão: {temperatura}");

            return new
            {
                Fahrenheit = temperatura,
                Celsius = ConversorTemperatura.FahrenheitParaCelsius(temperatura)
            };
        }
    }
}

Será o método FahrenheitParaCelsius definido no tipo estático ConversorTemperatura o foco dos testes automatizados (há aqui um erro proposital que levará a uma falha nos testes, devido à ausência de arredondamento para 2 casas decimais):

using System;

namespace APITemperatura
{
    public static class ConversorTemperatura
    {
        public static double FahrenheitParaCelsius(double temperatura)
        {
            if (temperatura < -459.67)
            {
                throw new Exception(
                    $"Valor de temperatura em Fahrenheit inválido: {temperatura}");
            }
                
            return (temperatura - 32) / 1.8; // Simulação proposital de falha
        }
    }
}

No projeto APITemperatura.Testes (baseado no template de xUnit para .NET Core) foi definida a classe TestesConversorTemperatura:

  • O método TestarConversaoTemperatura faz uso do atributo Theory e receberá vários conjuntos de parâmetros especificados para a execução de testes por meio do atributo InlineData;
  • Ao invés de Assert.Equal quando optamos pelo uso de Fluent Assertions a instrução valorCalculado.Should().Be() permitiu a obtenção de um código mais intuitivo, inclusive com a geração de uma mensagem customizada de falha (através do segundo parâmetro fornecido ao método Be()).
using Xunit;
using FluentAssertions;

namespace APITemperatura.Testes
{
    public class TestesConversorTemperatura
    {
        [Theory]
        [InlineData(32, 0)]
        [InlineData(86, 30)]
        [InlineData(90.5, 32.5)]
        [InlineData(212, 100)]
        [InlineData(0, -17.78)]
        public void TestarConversaoTemperatura(
            double fahrenheit, double celsius)
        {
            double valorCalculado =
                ConversorTemperatura.FahrenheitParaCelsius(fahrenheit);
            valorCalculado.Should().Be(celsius,
                "| Falha na conversão de temperaturas: " +
                $"Vl. Fahrenheit = {fahrenheit} | " +
                $"Vl. Esperado Celsius = {celsius} | " +
                $"Vl. Calculado Celsius = {valorCalculado} |");
        }
    }
}

Ao executarmos os testes a partir do Visual Studio 2019 teremos um resultado similar ao da próxima imagem, sendo possível notar a mensagem customizada especificada via Fluent Assertions:

Image for post

Criando o workflow para testes, build e deployment automatizados

As configurações para build e deployment de uma aplicação no Azure App Service já foram detalhadas no vídeo indicado no início deste post e também no artigo:

GitHub Actions: exemplos de workflows para build e deployment em várias tecnologias

Para os testes, build e deployment automatizados aqui descritos utilizei o workflow do .NET Core:

Image for post

A seguir está o código do workflow, já contemplando a execução de testes (linha 27), build (linha 29 em diante) e deployment no Azure App Service (a partir da linha 35):

name: ASP.NET Core 3.1 + Tests + Azure App Service

on:
  push:
    branches: [ master ]
  pull_request:
    branches: [ master ]

jobs:
  build:

    env:
      CAMINHO_PROJETO_TESTES: ./APITemperatura.Testes/APITemperatura.Testes.csproj
      CAMINHO_PROJETO_WEBAPP: ./APITemperatura
      NOME_WEBAPP_AZURE: groffegithubactions

    runs-on: ubuntu-latest

    steps:
    - uses: actions/checkout@v2
    - name: Setup .NET Core
      uses: actions/setup-dotnet@v1
      with:
        dotnet-version: 3.1.301

    - name: Executar testes automatizados
      run: dotnet test ${{ env.CAMINHO_PROJETO_TESTES }} --verbosity minimal

    - name: Build com o utilitario dotnet
      run: dotnet build ${{ env.CAMINHO_PROJETO_WEBAPP }} --configuration Release

    - name: Publish com o utilitario dotnet
      run: dotnet publish ${{ env.CAMINHO_PROJETO_WEBAPP }} -c Release -o app

    - name: Login no Azure
      uses: Azure/login@v1
      with:
        creds: ${{ secrets.AZURE_CREDENTIALS }}
    
    - name: Deployment no Azure App Service
      uses: Azure/webapps-deploy@v2
      with:
        app-name: ${{ env.NOME_WEBAPP_AZURE }}
        package: './app'

A instrução dotnet test ${{ env.CAMINHO_PROJETO_TESTES }} –verbosity minimal executará os testes definidos no projeto APITemperatura.Testes, retornando erro num primeiro momento e interrompendo a execução do workflow :

Image for post

É possível notar nos detalhamentos inclusive a mensagem configurada via Fluent Assertions:

Image for post

Alterando o método FahrenheitParaCelsius da classe ConversorTemperatura para que realize o arredondamento com 2 casas decimais:

using System;

namespace APITemperatura
{
    public static class ConversorTemperatura
    {
        public static double FahrenheitParaCelsius(double temperatura)
        {
            if (temperatura < -459.67)
            {
                throw new Exception(
                    $"Valor de temperatura em Fahrenheit inválido: {temperatura}");
            }
                
            return Math.Round((temperatura - 32) / 1.8, 2);
        }
    }
}

Ocorrerá uma nova execução do workflow, desta vez com sucesso nos testes e a consequente publicação do projeto no recurso do Azure App Service:

Image for post

A próxima imagem traz um exemplo de requisição processada pela aplicação de testes (logo após o deployment):

Image for post