Skip to content

psielta/csharp-xunit-testing-examples

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

Testes Unitarios com xUnit e FluentAssertions

Projeto de estudo e demonstracao pratica de testes unitarios em C# utilizando xUnit e FluentAssertions. Os exemplos sao baseados em problemas classicos do LeetCode para facilitar o aprendizado.

Sobre o Projeto

Este repositorio contem implementacoes de algoritmos simples do LeetCode com foco total na escrita de testes unitarios de qualidade. O objetivo e demonstrar boas praticas como:

  • Padrao AAA (Arrange-Act-Assert)
  • Uso do framework xUnit
  • Assercoes fluentes com FluentAssertions
  • Organizacao de testes com regions
  • Testes parametrizados com Theory/InlineData

Estrutura do Projeto

backend/
├── ConsoleAppPalindrome/           # Problema: Is Palindrome (LeetCode #9)
│   ├── Palindrome/
│   │   ├── Services/
│   │   │   └── PalindromeService.cs
│   │   └── Program.cs
│   ├── Palindrome.Tests/
│   │   └── PalindromeServiceTests.cs
│   └── Palindrome.sln
│
├── ConsoleAppTwoSumProj/           # Problema: Two Sum (LeetCode #1)
│   ├── TwoSum/
│   │   ├── Services/
│   │   │   └── TwoSumService.cs
│   │   └── Program.cs
│   ├── TwoSum.Tests/
│   │   └── TwoSumServiceTests.cs
│   └── TwoSum.sln
│
└── README.md

Problemas Implementados

Problema LeetCode Descricao
Palindrome Number #9 Verificar se um numero e palindromo
Two Sum #1 Encontrar dois numeros que somam um alvo

O que e xUnit?

xUnit e um framework de testes unitarios gratuito e de codigo aberto para .NET. Ele e usado para escrever e executar testes automatizados em aplicacoes C#, F# e VB.NET. O xUnit foi criado pelo inventor original do NUnit v2 e e o framework de testes usado pela equipe do .NET Core.

Principais Anotacoes do xUnit

[Fact]

A anotacao [Fact] marca um metodo como um teste que deve ser executado. E usada para testes simples que nao requerem parametros.

[Fact]
public void IsPalindrome_WithZero_ReturnsTrue()
{
    // Arrange
    int number = 0;

    // Act
    bool result = PalindromeService.IsPalindrome(number);

    // Assert
    result.Should().BeTrue(because: "zero is a single digit and reads the same forwards and backwards");
}

Quando usar [Fact]:

  • Testes com um unico cenario
  • Casos especificos como valores limites (zero, MaxValue, MinValue)
  • Testes que nao precisam de variacao de dados

[Theory]

A anotacao [Theory] e usada para testes parametrizados - testes que sao executados multiplas vezes com diferentes conjuntos de dados. Deve ser combinada com atributos de dados como [InlineData].

[Theory]
[InlineData(121, true)]
[InlineData(-121, false)]
[InlineData(10, false)]
public void IsPalindrome_WithRequiredTestCases_ReturnsExpectedResult(int number, bool expected)
{
    // Este teste sera executado 3 vezes, uma para cada InlineData
    bool result = PalindromeService.IsPalindrome(number);
    result.Should().Be(expected);
}

Quando usar [Theory]:

  • Testar a mesma logica com diferentes entradas
  • Reduzir duplicacao de codigo de teste
  • Cobrir multiplos cenarios de forma concisa

[InlineData]

A anotacao [InlineData] fornece dados de entrada para testes parametrizados. Cada [InlineData] representa uma execucao do teste com valores especificos.

[Theory]
[InlineData(11)]    // Primeira execucao com 11
[InlineData(22)]    // Segunda execucao com 22
[InlineData(33)]    // Terceira execucao com 33
public void IsPalindrome_WithTwoDigitPalindromes_ReturnsTrue(int number)
{
    bool result = PalindromeService.IsPalindrome(number);
    result.Should().BeTrue();
}

Tipos suportados no InlineData:

  • Tipos primitivos: int, string, bool, double, etc.
  • Arrays: int[], string[]
  • null para tipos anulaveis

FluentAssertions

O codigo usa a biblioteca FluentAssertions para tornar as assercoes mais legiveis e expressivas. Em vez de usar os asserts tradicionais, usamos uma sintaxe fluente que se le quase como ingles.

Comparacao: Assert Tradicional vs FluentAssertions

// Assert tradicional (xUnit)
Assert.True(result);
Assert.Equal(expected, actual);

// FluentAssertions (mais legivel)
result.Should().BeTrue();
actual.Should().Be(expected);

Principais Metodos do FluentAssertions

Assercoes Booleanas

result.Should().BeTrue();      // Verifica se o valor e verdadeiro
result.Should().BeFalse();     // Verifica se o valor e falso

Assercoes de Igualdade

actual.Should().Be(expected);           // Igualdade simples
actual.Should().NotBe(unexpected);      // Diferenca

Assercoes de Colecoes

result.Should().BeEquivalentTo([0, 1]);     // Arrays com mesmos elementos
result.Should().HaveCount(2);               // Tamanho da colecao
result.Should().Contain(5);                 // Contem elemento
result.Should().BeEmpty();                  // Esta vazio

Assercoes de Excecoes

Action act = () => _sut.TwoSum(null!, target);

act.Should().Throw<ArgumentNullException>()
    .WithParameterName("nums");

act.Should().Throw<ArgumentException>()
    .WithMessage("*nao possui 02 itens*");

Parametro because:

O parametro because: adiciona uma mensagem explicativa quando a assercao falha, facilitando o debug:

result.Should().BeTrue(because: "zero is a single digit and reads the same forwards and backwards");

result.Should().BeFalse(because: "negative numbers have a minus sign which breaks symmetry");

Padrao AAA (Arrange-Act-Assert)

O padrao AAA e uma forma de organizar os testes em tres secoes claras:

[Fact]
public void TwoSum_WithValidArrayAndTarget_ReturnsCorrectIndices()
{
    // Arrange - Preparacao dos dados e objetos
    int[] nums = [2, 7, 11, 15];
    int target = 9;

    // Act - Execucao do metodo sendo testado
    int[] result = _sut.TwoSum(nums, target);

    // Assert - Verificacao do resultado
    result.Should().BeEquivalentTo([0, 1]);
}

As Tres Secoes

1. Arrange (Organizar)

Prepara todos os dados e objetos necessarios para o teste:

  • Criar instancias de classes
  • Definir valores de entrada
  • Configurar mocks (se necessario)
// Arrange
int[] nums = [2, 7, 11, 15];
int target = 9;

2. Act (Agir)

Executa o metodo ou operacao que esta sendo testado:

  • Deve ser uma unica linha quando possivel
  • E a acao principal do teste
// Act
int[] result = _sut.TwoSum(nums, target);

3. Assert (Afirmar)

Verifica se o resultado e o esperado:

  • Uma ou mais verificacoes
  • Usa FluentAssertions para clareza
// Assert
result.Should().BeEquivalentTo([0, 1]);

Organizacao com #region

O codigo usa #region e #endregion para agrupar testes relacionados, facilitando a navegacao:

#region Required Test Cases
// Casos de teste obrigatorios do LeetCode
#endregion

#region Edge Cases
// Casos extremos ou especiais (zero, negativos, etc)
#endregion

#region Two Digit Numbers
// Testes com numeros de dois digitos
#endregion

#region Large Numbers
// Testes com numeros grandes
#endregion

#region Numbers with Zeros
// Testes com zeros
#endregion

#region Boundary Values
// Valores nos limites (int.MaxValue, int.MinValue)
#endregion

Exemplo Completo Explicado

Vamos analisar um teste especifico em detalhes:

[Theory]
[InlineData(121, true)]    // Palindromo: le-se igual de frente para tras
[InlineData(-121, false)]  // Numeros negativos nao sao palindromos
[InlineData(10, false)]    // Nao e palindromo: 10 != 01
public void IsPalindrome_WithRequiredTestCases_ReturnsExpectedResult(int number, bool expected)
{
    // Act - Executa o metodo IsPalindrome
    bool result = PalindromeService.IsPalindrome(number);

    // Assert - Verifica se o resultado e o esperado
    result.Should().Be(expected,
        because: $"{number} should {(expected ? "" : "not ")}be a palindrome");
}

Explicacao linha por linha:

  1. [Theory] - Indica que e um teste parametrizado
  2. [InlineData(121, true)] - Primeiro caso: 121 deve retornar true
  3. [InlineData(-121, false)] - Segundo caso: -121 deve retornar false
  4. [InlineData(10, false)] - Terceiro caso: 10 deve retornar false
  5. O teste sera executado 3 vezes, uma para cada InlineData
  6. result.Should().Be(expected) - Verifica se o resultado corresponde ao esperado
  7. because: - Mensagem dinamica explicando o por que

Testando Excecoes

Testar excecoes e fundamental para garantir que o codigo trata erros corretamente:

[Fact]
public void TwoSum_WithNullArray_ThrowsArgumentNullException()
{
    // Arrange
    int[]? nums = null;
    int target = 5;

    // Act - Captura a acao que deve lancar excecao
    Action act = () => _sut.TwoSum(nums!, target);

    // Assert - Verifica que a excecao foi lancada
    act.Should().Throw<ArgumentNullException>()
        .WithParameterName("nums");
}

[Fact]
public void TwoSum_WithNoSolution_ThrowsArgumentException()
{
    // Arrange
    int[] nums = [1, 2, 3, 4];
    int target = 100;

    // Act
    Action act = () => _sut.TwoSum(nums, target);

    // Assert
    act.Should().Throw<ArgumentException>()
        .WithMessage("*nao possui 02 itens*");
}

Pontos importantes:

  • Usamos Action para encapsular a chamada
  • WithParameterName() verifica qual parametro causou o erro
  • WithMessage() suporta wildcards (*) para mensagens parciais

Nomenclatura de Testes

Este projeto segue o padrao de nomenclatura:

[MetodoTestado]_[Cenario]_[ResultadoEsperado]

Exemplos:

  • IsPalindrome_WithZero_ReturnsTrue
  • TwoSum_WithNullArray_ThrowsArgumentNullException
  • IsPalindrome_WithNegativeSingleDigit_ReturnsFalse

Executando os Testes

Via Linha de Comando

# Executar todos os testes de uma solution
dotnet test ConsoleAppPalindrome/Palindrome.sln
dotnet test ConsoleAppTwoSumProj/TwoSum.sln

# Executar com output detalhado
dotnet test --verbosity normal

# Executar testes especificos
dotnet test --filter "FullyQualifiedName~Palindrome"

Via IDE

  • Visual Studio: Test Explorer (Ctrl+E, T)
  • Rider: Unit Tests window
  • VS Code: Test Explorer extension

Tecnologias Utilizadas

Tecnologia Versao Proposito
.NET 8.0 / 10.0 Runtime
xUnit 2.x Framework de testes
FluentAssertions 6.x Assercoes fluentes
C# 12 Linguagem

Vantagens do xUnit

  • Sintaxe moderna e limpa
  • Suporte a testes parametrizados com [Theory]
  • Execucao paralela de testes por padrao
  • Otima integracao com .NET Core e .NET 5+
  • Extensivel e personalizavel
  • Amplamente usado pela comunidade .NET

Como Contribuir

Este e um projeto de portfolio pessoal, mas feedbacks sao bem-vindos! Abra uma issue ou envie um PR.


Autor

Mateus Salgueiro


Desenvolvido como parte do meu portfolio de desenvolvimento .NET, demonstrando boas praticas de testes unitarios.

About

Practical examples of unit testing in C# with xUnit and FluentAssertions using LeetCode problems

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages