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.
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
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
| Problema | LeetCode | Descricao |
|---|---|---|
| Palindrome Number | #9 | Verificar se um numero e palindromo |
| Two Sum | #1 | Encontrar dois numeros que somam um alvo |
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.
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
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
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[] nullpara tipos anulaveis
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.
// Assert tradicional (xUnit)
Assert.True(result);
Assert.Equal(expected, actual);
// FluentAssertions (mais legivel)
result.Should().BeTrue();
actual.Should().Be(expected);result.Should().BeTrue(); // Verifica se o valor e verdadeiro
result.Should().BeFalse(); // Verifica se o valor e falsoactual.Should().Be(expected); // Igualdade simples
actual.Should().NotBe(unexpected); // Diferencaresult.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 vazioAction act = () => _sut.TwoSum(null!, target);
act.Should().Throw<ArgumentNullException>()
.WithParameterName("nums");
act.Should().Throw<ArgumentException>()
.WithMessage("*nao possui 02 itens*");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");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]);
}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;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);Verifica se o resultado e o esperado:
- Uma ou mais verificacoes
- Usa FluentAssertions para clareza
// Assert
result.Should().BeEquivalentTo([0, 1]);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)
#endregionVamos 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:
[Theory]- Indica que e um teste parametrizado[InlineData(121, true)]- Primeiro caso: 121 deve retornartrue[InlineData(-121, false)]- Segundo caso: -121 deve retornarfalse[InlineData(10, false)]- Terceiro caso: 10 deve retornarfalse- O teste sera executado 3 vezes, uma para cada
InlineData result.Should().Be(expected)- Verifica se o resultado corresponde ao esperadobecause:- Mensagem dinamica explicando o por que
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
Actionpara encapsular a chamada WithParameterName()verifica qual parametro causou o erroWithMessage()suporta wildcards (*) para mensagens parciais
Este projeto segue o padrao de nomenclatura:
[MetodoTestado]_[Cenario]_[ResultadoEsperado]
Exemplos:
IsPalindrome_WithZero_ReturnsTrueTwoSum_WithNullArray_ThrowsArgumentNullExceptionIsPalindrome_WithNegativeSingleDigit_ReturnsFalse
# 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"- Visual Studio: Test Explorer (Ctrl+E, T)
- Rider: Unit Tests window
- VS Code: Test Explorer extension
| Tecnologia | Versao | Proposito |
|---|---|---|
| .NET | 8.0 / 10.0 | Runtime |
| xUnit | 2.x | Framework de testes |
| FluentAssertions | 6.x | Assercoes fluentes |
| C# | 12 | Linguagem |
- 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
Este e um projeto de portfolio pessoal, mas feedbacks sao bem-vindos! Abra uma issue ou envie um PR.
Mateus Salgueiro
- GitHub: @psielta
- LinkedIn: Mateus Salgueiro
Desenvolvido como parte do meu portfolio de desenvolvimento .NET, demonstrando boas praticas de testes unitarios.