Cell CMS - Criando testes de maneira prática
Tags: csharp, testing, tutorial, dotnet
Published: 2021-01-31 18:17:33

## Intro

No [último post](https://dev.to/ardc_overflow/cell-cms-utilizando-docker-para-criar-containers-3h4o) falamos sobre como utilizar Docker e suas ferramentas de suporte dentro do Visual Studio, quais são suas vantagens e como Containers resolvem vários problemas "clássicos" do deploy.

No post de hoje vamos fazer algo que deveríamos ter feito desde o começo do Cell CMS: Criar nossos projetos de testes unitários.

O branch principal para o post de hoje será o `feature/create-tests`.

{% github rodolphocastro/cell-cms %}

[Cover do artigo pega lá no unDraw](https://undraw.co/)

## Últimas alterações do Cell CMS

Faz bastante tempo desde o meu último post. Pois é! Infelizmente acabei de distraindo com outros afazeres e estudos e acabei deixando de lado o bom hábito de escrever 😅.

Mas o Cell CMS teve algumas alterações entre o último post e este! De maneira resumida:

1. Migramos para .NET 5 (originalmente estávamos no .NET Core 3.1)
2. Criamos uma pipeline no GitHub Actions para compilar o projeto continuamente
3. Inúmeras alterações no meu ambiente de desenvolvimento

Eventualmente escreverei um pouco sobre estes processos, provavelmente começando pelo do ambiente (em breve devo reconfigurar _mais uma vez_). Prometo!

Sem mais delongas, vamos para o conteúdo em si!

## Testes Unitários: O que são, de onde vieram e para que servem

De maneira bem resumida podemos dizer que **testes unitários** _(na programação orientada a objetos)_ **são rotinas automatizadas para garantir o funcionamento de uma classe**.

Mas o que isso quer dizer, na prática?

Na prática isso quer dizer que você terá um conjunto de classes e métodos especializadas em testar de maneira rápida cada classe que compõe o seu sistema, mas preste atenção pois **eles não fazem tudo**! Como o próprio nome já diz o **escopo deste teste é a unidade** (que, eventualmente, compõem o todo do seu sistema 🤷‍♂️). 

Outra grande vantagem de ter uma boa cobertura de testes unitários é poder refatorar sem medo, afinal os testes vão garantir que você não quebrou nenhuma API pública (ou se você precisou quebrar pelo menos vai te lembrar de avisar os consumidores dessa API como um `[Obsolete]` antes de remover de vez!).

Se você quiser saber mais sobre refatorar um amigo meu está fazendo uma série de vídeos especialmente sobre isso, dê uma olhada no canal [AspiraPlay](https://www.youtube.com/c/AspiraPlay/featured) no YouTube!

Existem, além dos testes unitários, vários outros tipos de testes (manuais e automatizados) mas para o desenvolvedor o teste mais rápido de fazer e executar será, sempre, o unitário. E por isso vamos falar primeiros deles antes de pensarmos em testes de integração, funcionalidades, aceite, etc.

Uma boa leitura para esse assunto é o [próprio site do grande Martin Fowler](https://martinfowler.com/testing/).

### Como escrever um bom teste unitário

Seja qual for o framework ou linguagem de programação que você esteja utilizando sempre encontrará essas características em um bom teste unitário.

#### Nome do Teste

O primeiro elemento importante é o **nome do seu teste**. É no nome em que vamos dar aquela resumida em:

1. O que estamos testando
2. Sob quais condições/estado estamos testando
3. O que esperamos

Em alguns frameworks o **nome do teste será o nome do método que executa a rotina de teste**, em outros são utilizadas anotações, classes, atributos, comentários... Mas a constante é sempre essa:

> Você deve identificar o que está testando, sob quais condições e o que você espera de resultado.

#### Entradas (Inputs)

O segundo elemento importante para seu teste é identificar **as entradas** para a rotina que você está testando. Note que isso poderá mudar *bastante* mas você sempre deve pensar em isolar bem o que você quer que entre no seu teste para que garanta que ele é preciso!

Alguns exemplos de testes e inputs:

1. Testar uma classe que cria um JSON -> Input seriam objetos diferentes
2. Testar uma classe que utiliza outra para salvar algo -> Input seria a classe que será utilizada via _mock_ e o dado que seria salvo
3. Testar uma regra de negócio -> Input que consiga disparar esta regra de negócio e inputs que falhem a regra de negócio

Não se preocupe ainda com o que é um _mock_, falaremos disso depois!

#### O que está sendo testado (Subject)

O terceiro elemento (que **apesar da ordem é o mais importante!**) é o que você quer testar de verdade!

A importância de saber quem é este elemento é vital para que seu teste seja unitário de verdade. **Se você não consegue identificar um único elemento você provavelmente está escrevendo um teste de integração ou você precisa rever as dependências de sua classe.**

#### O estado do que está sendo testado (State)

Em quarto lugar você precisa pensar qual o estado que você quer testar. Isso pode ser:

1. Uma dependência falhando
2. Uma input inválida
3. Uma referência nula
4. Um parâmetro sendo omitido

**Nunca tente testar todos os possíveis estados**! Uma cobertura de 100% de testes pode significar que você está investindo mais tempo em testes do que funcionalidades novas!

Minha preferência *pessoal* é começar testando sempre dois estados: **O válido e o principal inválido**.

#### O que é esperado

_Finalmente_ o último elemento é: **o que você espera que aconteça?**

Por exemplo:

1. Retorne null
2. Retorne um objeto válido
3. Lance uma `Exception`
4. Dê timeout após ... milissegundos 

### Juntando tudo: AAA

Como lembrar de tudo isso? Uma das práticas mais comuns é sempre lembras dos 3 As:

1. **Arrange**: Escolha as inputs, o que será testado e monte o cenário
2. **Act**: Realize a ação que você quer que seja testada
3. **Assert**: Verifique que a ação fez o que você esperava

Eu normalmente abro meus testes já escrevendo 3 linhas de comentários e então vou preenchendo a lógica: 

```{csharp}
// Arrange
var subject = new MinhaClasseSendoTestada(null);

// Act
var result = subject.FazAlgumaCoisa();

// Assert
Assert.IsNull(result);
```

### Mocks e Stubs

Não sou um expert no assunto (dê uma pesquisada sobre Kent Beck, TDD Chicago e London para saber mais sobre isso) mas de maneira bem resumida:

**Um Mock é um objeto que simula o comportamento de outro objeto**, permitindo que você controle o que o objeto real faria e valide quantas vezes ele foi chamado, com quais parâmetros, etc. Você estará testando por *comportamento*.

**Um STUB é um objeto que simula apenas o retorno de outro objeto**, entregando respostas fixas para chamadas fixas, controlando o que é retornado quando chamariam o objeto real e apenas isso. Você estará testando por **estado**, maior parte das vezes, porém também poderia testar por *comportamento* com algumas alterações.

## Unit Tests com .NET

Agora que temos uma ideia do que são testes unitários vamos pensar neles no universo do .NET, de cara já podemos dizer que existem 3 frameworks populares de testes unitários:

1. **nUnit** -> Mais clássico mas amplamente utilizado, com diversos plugins e runners
2. **xUnit** -> Mais moderno e próximo à ideia de TDD, também é amplamente utilizado
3. **MSTest** -> Não recomendado mais atualmente

A principal diferença que nós, como desenvolvedores, vamos notar entre o nUnit e o xUnit é a maneira com que eles lidam com as classes de teste.

Por padrão **o xUnit cria uma instância da classe para executar cada método de teste**. Você sempre terá uma certa segurança de que o estado da sua classe de teste está limpo.

Enquanto isso **o nUnit utiliza a mesma instância da classe para executar todos os métodos de teste**. Você terá de tomar cuidado com instâncias que podem ser alteradas pelos testes em si, levando a interdependências, travando a ordem de execução, etc...

Porém, seja xUnit ou nUnit, as classes de teste conterão os seguintes elementos:

1. `Métodos()` indicando as rotinas de teste
2. `[Atributos]` indicando que um método é um teste com ou sem parâmetros
3. `[OutrosAtributos]` indicando métodos de configuração e limpeza dos seus testes

Não vou entrar a fundo nos atributos e setups de nenhum dos dois, porém na seção de codificação mesmo você poderá ver como fica uma classe de teste utilizando o xUnit!

### AutoFixture - Criação automática de massa de dados

Comentamos acima sobre a necessidade de sabermos as inputs dos nossos testes, certo? Porém as vezes você **sabe o que precisa de input** mas **não se importa com os detalhes**, certo? Nesses cenários pode ser tornar chato você ter de sempre adicionar N parâmetros no seu teste e sempre criar um `new ObjetoInput(param1, param2, ...);`. A biblioteca **AutoFixture** resolve exatamente isso!

De maneira sucinta:

> **AutoFixture gera** a massa de **dados para testes automaticamente para você**. Permitindo que você **gaste menos tempo na fase de Arrange** de seu teste.

E melhor ainda: Ele **funciona**, de cara, **sem nenhuma configuração** em boa parte dos casos, **porém você pode configurar ele com regras específicas para casos específicos**!

{% github AutoFixture/AutoFixture no-readme %}

### NSubstitute - Criação e Configuração de Mocks

A biblioteca **NSubstitute** é mais uma facilitadora para a nossa fase de **Arrange** dos testes. Lembra sobre mocks e stubs? Esse carinha aqui é quem vai criar os mocks pra gente.

De maneira curta:

> NSubstitute **cria**, automaticamente, **mocks para interfaces e métodos virtuais**. Além disso você poderá **controlar o que esses mocks retornam, quantas vezes podem ser chamados, etc**.

{% github nsubstitute/NSubstitute no-readme %}

> Nota: Por muito tempo utilizei uma outra biblioteca chamada [Moq](https://github.com/moq/moq) que faz a mesma coisa porém com sintaxe diferente. Ultimamente tenho dado preferência pelo NSubstitute exatamente por parecer legível.

Um breve exemplo do NSubstitute:

```{csharp}
interface IEmailPublisher {
    string Driver { get; }
    Task SendTo(string r, CancellationToken ct = default);
}

var publisher = Substitute.For<IEmailPublisher>();
publisher.SendTo("temp").Returns(Task.CompletedTask);
publisher.Driver.Returns("MyDriver123");
//publisher.SendTo().ThrowsForAnyArgs<NotImplementedException>();   // Caso queira simular um erro

var result = publisher.Driver;
Assert.Equal("MyDriver123", result);    // -> True
```

### FluentAssertions - Sintaxe fluent/builder para validar os cenários

FluentAssertions é uma biblioteca que não vai te ajudar a economizar tempo enquanto prepara seus testes mas **vai tornar seus testes mais legíveis** a longo prazo!

> **FluentAssertions** permite que você **descreva o que é esperado** do seu teste **usando uma linguagem** mais próxima à **natural**.

Por exemplo, se eu quisesse validar em um teste que: "O resultado não é nulo, é do tipo X e é equivalente ao objeto Y"

```{csharp}
// Usando fluent assertions
resultado.Should()
    .NotBeNull().And
    .BeOfType<X>().And
    .BeEquivalentTo(Y);

// Usando Asserts
Assert.NotNull(resultado);
Assert.IsType<X>(resultado);
Assert.IsEqual(resultado, Y);
```

{% github fluentassertions/fluentassertions no-readme %}

### EntityFrameworkCore.InMemory - Versão do EFCore para testes unitários

Normalmente utilizaríamos um Mock/Stub para simular o acesso ao banco em nossos testes unitários, porém os objetos e tipos do EntityFrameworkCore, apesar de "mockáveis", requerem um setup bem extenso.

Com isso em mente o próprio EntityFrameworkCore já criou uma biblioteca exatamente para que não precisemos fazer todo esse setup. O provider `InMemory` é a maneira de simular um acesso ao banco nos nossos testes unitários.

Existe uma discussão *bem extensa* sobre se usar isso não configuraria seu teste como um teste de integração e se a maneira correta não seria voltar ao velho padrão de Unit Of Work + Repositories (que o próprio EFCore já implementa por si só 🤷‍♂️).

Meu take pessoal nesse assunto é que devemos ser pragmáticos. A biblioteca está ai, pronta. **Salvo que eu precise muito por que vou criar mais uma camada?** Abstrações são boas (vida longa a interfaces e classe abstratas!) mas da mesma maneira que **você pode pecar por usar só implementação concretas você também pode pecar por criar abstrações para tudo!** 

Abstrair só por abstrair é overengineering.

{% github dotnet/efcore no-readme %}

## Mãos à obra: Criando nosso projeto de testes

Vamos à pratica! Fora do Visual Studio vamos criar uma nova pasta na raiz do projeto, chamada `tests/`. A ideia aqui é que todos os nossos projetos de testes (Unit, Integration e Feature) ficarão nesta pasta para não bagunçar a hierarquia das outras bibliotecas do projeto.

### Criando o Projeto e Instalando dependências

Pelo Visual Studio:

1. Clique direito na solução e escolha `Add new project`
2. Pesquise nos templates por `xUnit` e localize um chamado `xUnit Test Project (.NET Core)`
3. Escolha um nome para seu projeto e o coloque para ser salvo na pasta `tests/`
4. *Opcional, caso o projeto a ser testado seja .NET 5*: 
   1. Clique com o direito no projeto e escolha `Properties`
   2. Mude o campo `Target framework` para `.NET 5.0`
   3. Salve as alterações e recarregue o projeto

Com o projeto criado abra o `NuGet Manager` para o projeto de testes e adicione os seguintes pacotes:

1. `AutoFixture`
2. `AutoFixture.AutoNSubstitute`
3. `AutoFixture.Xunit2`
4. `FluentAssertions`
5. `Microsoft.EntityFrameworkCore.InMemory`
6. `NSubstitute`

Lembre-se de adicionar como referência ao projeto de testes o projeto que será testado.

### Configurando nossas Ferramentas

Com tudo instalado podemos passar para a parte de preparação. De certa maneira este passo é opcional mas se quisermos escrever nossos testes da maneira mais prática possível este passo será o grande diferencial de produtividade.

O `AutoFixture.Xunit2` nos trás um atributo `[AutoData]` que pode ser utilizado para que os parâmetros de um teste sejam criados automaticamente pelo `AutoFixture`.

A parte mais legal é que **herdar este atributo e customizar como o AutoFixture cria as coisas**. Isso significa que podemos colocar na "pipeline" do AutoFixture coisas como o `EntityFrameworkCore.InMemory`, `NSubstitute` e customizar como alguns atributos nossos seriam criados.

Para fazer isso tudo que precisamos é criar uma nova classe `CreateDataAttribute`, herdar a classe `AutoDataAttribute` e criar um construtor que chama o `base(Func<IFixture> factory)`.

Minha sugestão é que você crie um método estático que retorna uma `IFixture` e nesse método faça toda a configuração!

Por exemplo, esta é a minha versão deste atributo para o Cell CMS:

```{csharp}
using System;

using AutoFixture;
using AutoFixture.AutoNSubstitute;
using AutoFixture.Xunit2;

using AutoMapper;

using CellCms.Api;

using Microsoft.EntityFrameworkCore;

namespace CellCms.Tests.Unit.Utils
{
    /// <summary>
    /// Atributo para configurar automaticamente os dados de um test case.
    /// </summary>
    public class CreateDataAttribute : AutoDataAttribute
    {
        public CreateDataAttribute() : base(SetupCellCmsFixture)
        {

        }

        /// <summary>
        /// Configura uma fixture com todos os objetos necessários para
        /// testar o CellCMS.
        /// </summary>
        /// <returns></returns>
        private static IFixture SetupCellCmsFixture()
        {
            var fix = new Fixture();
            fix.Customize(new AutoNSubstituteCustomization());
            SetupRecursionBehaviors(fix);
            SetupCellContext(fix);
            SetupAutoMapper(fix);
            return fix;
        }

        /// <summary>
        /// Configura e Injeta uma instância do AutoMapper.
        /// </summary>
        /// <param name="fix"></param>
        private static void SetupAutoMapper(Fixture fix)
        {
            var autoMapperConfig = new MapperConfiguration(cfg =>
            {
                cfg.AddMaps(typeof(Startup));
            });
            fix.Inject(autoMapperConfig);
            fix.Inject(autoMapperConfig.CreateMapper());
        }

        /// <summary>
        /// Configura e Injeta uma instância do CellContext.
        /// </summary>
        /// <param name="fix"></param>
        private static void SetupCellContext(Fixture fix)
        {
            var dbOptions = new DbContextOptionsBuilder()
                            .UseInMemoryDatabase(Guid.NewGuid().ToString());
            fix.Inject(new CellContext(dbOptions.Options));
        }

        /// <summary>
        /// Configura o comportamento da Fixture durante
        /// chamadas recursivas.
        /// </summary>
        /// <param name="fix"></param>
        private static void SetupRecursionBehaviors(Fixture fix)
        {
            fix.Behaviors.Remove(new ThrowingRecursionBehavior());
            fix.Behaviors.Add(new OmitOnRecursionBehavior());
        }
    }
}
```

É bastante verboso na primeira olhada mas lembre-se que **usando esse atributo você terá muito mais produtividade para escrever seus testes!**

### Escrevendo nossos Testes

Finalmente podemos começar a escrever nossos testes!

Tudo que você precisa fazer agora é **criar uma nova classe**, **criar um método para o seu teste** e **adicionar os atributos**.

Por exemplo um dos testes do Cell CMS:

```{csharp}
[Theory]        // Indica ao xUnit que este teste tem parâmetros
[CreateData]    // Indica que os parâmetros serão criados através do atributo que criamos na seção anterior
// O [Frozen] indica que o AutoFixture deve retornar sempre esta mesma instância para todos que precisarem dentro deste método! É como se fosse um Singleton
public async Task Handle_ExistingContext_ReturnsList([Frozen] CellContext context,
    IEnumerable<Feed> feeds,
    ListAllFeedsHandler subject)
{
    // Note que deixamos que o próprio "objeto a ser testado" seja criado pelo AutoFixture, dessa maneira o mesmo context que ele passou aqui para este método será passado para o subject!

    // Arrange
    // Aqui estou garantindo que os dados criados pelo AutoFixture estão salvos no Context do EntityFramework
    context.AddRange(feeds);
    await context.SaveChangesAsync();

    // Act
    var result = await subject.Handle(new ListAllFeeds(), default);

    // Assert
    result
        .Should()
        .NotBeNull().And
        .HaveSameCount(context.Feeds);
}
```

### Executando os Testes

Para executar os testes por linha de comando use: `dotnet test` na pasta da solução.

![Executando testes pelo terminal](https://dev-to-uploads.s3.amazonaws.com/i/63efv2e7m5gtivl3pezj.gif)

Para executar pelo Visual Studio o principal será a janela `Test Explorer` (atalho: `Ctrl+E, T`). Porém temos alguns outros atalhos importantes e práticos:

1. `Ctrl+R, A`: Executar todos os testes;
2. `Ctrl+R, Ctrl+A`: Executar todos os testes com Debug;
3. `Ctrl+R, T`: Executar o teste selecionado (no caso o teste em que seu cursor estiver);
4. `Ctrl+R, Ctrl+T`: Executar o teste selecionado com Debug;

![Test Explorer do Visual Studio](https://dev-to-uploads.s3.amazonaws.com/i/z0oh9r2aoskbwwnt2pbg.png)

## Finalizando

O post de hoje vai ficando por aqui! Espero que após ler esse post fique evidente **que podemos escrever testes unitários de maneira rápida e prática** graças a diversas bibliotecas *Open Source*!

Alguns assuntos que ainda quero abordar, para os próximos posts:

1. Analyzers
2. Refactoring
3. Domain Driven Design
4. Clean Architecture
5. Configuração de um ambiente de desenvolvimento

Fiquem ligados para o próximo post, obrigado por lerem e até a próxima!