Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Cell CMS - Criando testes de maneira prática
Rodolpho Alves
Rodolpho Alves

Posted on • Edited on

Cell CMS - Criando testes de maneira prática

Intro

Noúltimo post 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á ofeature/create-tests.

GitHub logo rodolphocastro / cell-cms

CMS leve, self-contained e prático de utilizar! Feito por desenvolvedores e para desenvolvedores!

Cell CMS

BranchStatusDescrição
MasterBuild and TestCiclo estável, recomendado para produção
DevelopBuild and TestCiclo em desenvolvimento, recomendado para entusiastas

Cell CMS é um content management system que visa ser:

  • Leve
  • Auto Contido (self-contained)
  • Prático de Utilizar

Nosso foco é em disponibilizar um CMS que desenvolvedores possam facilmente referenciar em seus aplicativos, sites e sistemas.

📚 Instruções

Utilizando uma Versão publicada

WIP, iremos suportar imagens Docker e executáveis

Compilando

Vocêprecisará ter instalado em seu ambiente oSDK 5.0.101 do Dotnet.

Uma vez configurado basta executardotnet build .\cell-cms.sln na raiz do repositório.

Testando

Executedotnet test .\cell-cms.sln na raiz do repositório.

Caso queira capturar informações de cobertura de testes utilize:

dotnet test --no-restore --collect:"XPlat Code Coverage" .\cell-cms.sln

Configurações

Autenticação/Autorização

O CellCMS utiliza oAzure Active Directory comoprovider de identidade, então você terá de configurar sua instância doAAD conforme explicadoneste post.

As seguintes variáveis de ambiente…

Cover do artigo pega lá no unDraw

Ú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 reconfigurarmais 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 quetestes 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 poiseles não fazem tudo! Como o próprio nome já diz oescopo 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 canalAspiraPlay 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 é opróprio site do grande Martin Fowler.

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 é onome 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 onome 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 é identificaras entradas para a rotina que você está testando. Note que isso poderá mudarbastante 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 viamock 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 é ummock, falaremos disso depois!

O que está sendo testado (Subject)

O terceiro elemento (queapesar 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ênciapessoal é 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 umaException
  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:

// Arrangevarsubject=newMinhaClasseSendoTestada(null);// Actvarresult=subject.FazAlgumaCoisa();// AssertAssert.IsNull(result);
Enter fullscreen modeExit fullscreen mode

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 porcomportamento.

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 porestado, maior parte das vezes, porém também poderia testar porcomportamento 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ãoo 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 issoo 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 masnã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 umnew ObjetoInput(param1, param2, ...);. A bibliotecaAutoFixture resolve exatamente isso!

De maneira sucinta:

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

E melhor ainda: Elefunciona, 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 logo AutoFixture / AutoFixture

AutoFixture is an open source library for .NET designed to minimize the 'Arrange' phase of your unit tests in order to maximize maintainability. Its primary goal is to allow developers to focus on what is being tested rather than how to setup the test scenario, by making it easier to create object graphs containing test data.

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

A bibliotecaNSubstitute é mais uma facilitadora para a nossa fase deArrange dos testes. Lembra sobre mocks e stubs? Esse carinha aqui é quem vai criar os mocks pra gente.

De maneira curta:

NSubstitutecria, 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 logo nsubstitute / NSubstitute

A friendly substitute for .NET mocking libraries.

Nota: Por muito tempo utilizei uma outra biblioteca chamadaMoq 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:

interfaceIEmailPublisher{stringDriver{get;}TaskSendTo(stringr,CancellationTokenct=default);}varpublisher=Substitute.For<IEmailPublisher>();publisher.SendTo("temp").Returns(Task.CompletedTask);publisher.Driver.Returns("MyDriver123");//publisher.SendTo().ThrowsForAnyArgs<NotImplementedException>();   // Caso queira simular um errovarresult=publisher.Driver;Assert.Equal("MyDriver123",result);// -> True
Enter fullscreen modeExit fullscreen mode

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 masvai tornar seus testes mais legíveis a longo prazo!

FluentAssertions permite que vocêdescreva o que é esperado do seu testeusando 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"

// Usando fluent assertionsresultado.Should().NotBeNull().And.BeOfType<X>().And.BeEquivalentTo(Y);// Usando AssertsAssert.NotNull(resultado);Assert.IsType<X>(resultado);Assert.IsEqual(resultado,Y);
Enter fullscreen modeExit fullscreen mode

GitHub logo fluentassertions / fluentassertions

A very extensive set of extension methods that allow you to more naturally specify the expected outcome of a TDD or BDD-style unit tests. Targets .NET Framework 4.7, as well as .NET Core 2.1, .NET Core 3.0, .NET 6, .NET Standard 2.0 and 2.1. Supports the unit test frameworks MSTest2, NUnit3, XUnit2, MSpec, and NSpec3.

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 providerInMemory é a maneira de simular um acesso ao banco nos nossos testes unitários.

Existe uma discussãobem 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 quevocê 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 logo dotnet / efcore

EF Core is a modern object-database mapper for .NET. It supports LINQ queries, change tracking, updates, and schema migrations.

Mãos à obra: Criando nosso projeto de testes

Vamos à pratica! Fora do Visual Studio vamos criar uma nova pasta na raiz do projeto, chamadatests/. 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 escolhaAdd new project
  2. Pesquise nos templates porxUnit e localize um chamadoxUnit Test Project (.NET Core)
  3. Escolha um nome para seu projeto e o coloque para ser salvo na pastatests/
  4. Opcional, caso o projeto a ser testado seja .NET 5:
    1. Clique com o direito no projeto e escolhaProperties
    2. Mude o campoTarget framework para.NET 5.0
    3. Salve as alterações e recarregue o projeto

Com o projeto criado abra oNuGet 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.

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

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

Para fazer isso tudo que precisamos é criar uma nova classeCreateDataAttribute, herdar a classeAutoDataAttribute e criar um construtor que chama obase(Func<IFixture> factory).

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

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

usingSystem;usingAutoFixture;usingAutoFixture.AutoNSubstitute;usingAutoFixture.Xunit2;usingAutoMapper;usingCellCms.Api;usingMicrosoft.EntityFrameworkCore;namespaceCellCms.Tests.Unit.Utils{/// <summary>/// Atributo para configurar automaticamente os dados de um test case./// </summary>publicclassCreateDataAttribute:AutoDataAttribute{publicCreateDataAttribute():base(SetupCellCmsFixture){}/// <summary>/// Configura uma fixture com todos os objetos necessários para/// testar o CellCMS./// </summary>/// <returns></returns>privatestaticIFixtureSetupCellCmsFixture(){varfix=newFixture();fix.Customize(newAutoNSubstituteCustomization());SetupRecursionBehaviors(fix);SetupCellContext(fix);SetupAutoMapper(fix);returnfix;}/// <summary>/// Configura e Injeta uma instância do AutoMapper./// </summary>/// <param name="fix"></param>privatestaticvoidSetupAutoMapper(Fixturefix){varautoMapperConfig=newMapperConfiguration(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>privatestaticvoidSetupCellContext(Fixturefix){vardbOptions=newDbContextOptionsBuilder().UseInMemoryDatabase(Guid.NewGuid().ToString());fix.Inject(newCellContext(dbOptions.Options));}/// <summary>/// Configura o comportamento da Fixture durante/// chamadas recursivas./// </summary>/// <param name="fix"></param>privatestaticvoidSetupRecursionBehaviors(Fixturefix){fix.Behaviors.Remove(newThrowingRecursionBehavior());fix.Behaviors.Add(newOmitOnRecursionBehavior());}}}
Enter fullscreen modeExit fullscreen mode

É bastante verboso na primeira olhada mas lembre-se queusando 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 eadicionar os atributos.

Por exemplo um dos testes do Cell CMS:

[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 SingletonpublicasyncTaskHandle_ExistingContext_ReturnsList([Frozen]CellContextcontext,IEnumerable<Feed>feeds,ListAllFeedsHandlersubject){// 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 EntityFrameworkcontext.AddRange(feeds);awaitcontext.SaveChangesAsync();// Actvarresult=awaitsubject.Handle(newListAllFeeds(),default);// Assertresult.Should().NotBeNull().And.HaveSameCount(context.Feeds);}
Enter fullscreen modeExit fullscreen mode

Executando os Testes

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

Executando testes pelo terminal

Para executar pelo Visual Studio o principal será a janelaTest 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

Finalizando

O post de hoje vai ficando por aqui! Espero que após ler esse post fique evidenteque podemos escrever testes unitários de maneira rápida e prática graças a diversas bibliotecasOpen 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!

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Engineering Manager and Fullstack Developer (on my free-time!), currently living in Brazil. Adept of DevOps and Agile Cultures, always looking forward improving my code and practices.
  • Location
    Paraná, Brazil
  • Education
    Bacharel em Sistemas de Informação @ Universidade Tecnológica Federal do Paraná
  • Work
    Engineering Manager at Trimble Inc
  • Joined

More fromRodolpho Alves

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp