| name | testing |
| description | Guia para testes (unitários, BDD, integração), cobertura, build e qualidade. Use quando a tarefa envolver testes, xUnit, BDD, SpecFlow, cobertura, mutation testing ou validação de build. |
Testing — Qualidade e Cobertura
Quando Usar
- Criar ou modificar testes (unitários, BDD, integração)
- xUnit, SpecFlow, BDD, cobertura de código
- Build, CI/CD, validação de qualidade
- Palavras-chave: "teste", "test", "BDD", "cobertura", "coverage", "xUnit", "build"
Princípios Essenciais
✅ Fazer
- Usar padrão AAA (Arrange, Act, Assert) em todos os testes
- FluentAssertions para assertions legíveis
- Moq para mockar dependências (interfaces)
- Fact para teste único, Theory para múltiplos casos
- Nomear testes:
MethodName_Scenario_ExpectedResult
- Testar comportamento, não implementação
- Cobertura mínima: 80% (UseCases, Domain, Validators)
❌ Não Fazer
- Nunca testar detalhes de implementação (mockar campos privados)
- Nunca testes dependentes uns dos outros (ordem importa)
- Nunca lógica complexa dentro de testes
- Nunca ignorar testes falhando (comentar
[Fact])
- Nunca esquecer de testar casos de erro (exceções, validações)
Regra de ouro: AAA + FluentAssertions + Moq = 80% dos testes unitários.
Checklist Rápido
- Criar projeto
<Projeto>.Tests.Unit (xUnit)
- Instalar:
xunit, FluentAssertions, Moq
- Estrutura de teste: Arrange → Act → Assert
- Nomear:
MethodName_Scenario_ExpectedResult
- Mockar dependências (interfaces) com Moq
- Usar
[Fact] (teste único) ou [Theory] (múltiplos casos)
- Cobertura:
dotnet test --collect:"XPlat Code Coverage"
- Meta: 80% de cobertura em UseCases, Domain, Validators
Exemplo Mínimo
Cenário: Teste unitário de UseCase com mocks
UseCase Sendo Testado
public class CreateUserUseCase(IUserRepository repository) : ICreateUserUseCase
{
public async Task<CreateUserOutputModel> ExecuteAsync(CreateUserInput input, CancellationToken ct = default)
{
if (await repository.ExistsAsync(input.Email, ct))
throw new InvalidOperationException("Email já está em uso.");
var user = await repository.CreateAsync(new User { Email = input.Email, Name = input.Name }, ct);
return new CreateUserOutputModel(user.Id, user.Email);
}
}
Testes (xUnit + Moq + FluentAssertions)
using Xunit;
using FluentAssertions;
using Moq;
public class CreateUserUseCaseTests
{
private readonly Mock<IUserRepository> _repositoryMock = new();
private readonly CreateUserUseCase _sut;
public CreateUserUseCaseTests()
{
_sut = new CreateUserUseCase(_repositoryMock.Object);
}
[Fact]
public async Task ExecuteAsync_WhenInputIsValid_ShouldCreateUser()
{
var input = new CreateUserInput("test@test.com", "Test User");
var expectedUser = new User { Id = Guid.NewGuid(), Email = input.Email, Name = input.Name };
_repositoryMock.Setup(x => x.ExistsAsync(input.Email, It.IsAny<CancellationToken>()))
.ReturnsAsync(false);
_repositoryMock.Setup(x => x.CreateAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(expectedUser);
var result = await _sut.ExecuteAsync(input);
result.Should().NotBeNull();
result.Email.Should().Be(input.Email);
_repositoryMock.Verify(x => x.CreateAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()), Times.Once);
}
[Fact]
public async Task ExecuteAsync_WhenEmailAlreadyExists_ShouldThrowInvalidOperationException()
{
var input = new CreateUserInput("existing@test.com", "Test");
_repositoryMock.Setup(x => x.ExistsAsync(input.Email, It.IsAny<CancellationToken>()))
.ReturnsAsync(true);
await _sut.Invoking(x => x.ExecuteAsync(input))
.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Email já está em uso.");
}
[Theory]
[InlineData("")]
[InlineData(" ")]
public async Task ExecuteAsync_WhenEmailIsEmpty_ShouldThrowArgumentException(string email)
{
var input = new CreateUserInput(email, "Test");
await _sut.Invoking(x => x.ExecuteAsync(input))
.Should().ThrowAsync<ArgumentException>();
}
}
Pontos-chave:
- Padrão AAA: Arrange (setup), Act (executar), Assert (verificar)
- Moq: Setup (mock comportamento), Verify (verificar chamadas)
- FluentAssertions:
.Should().Be(), .Should().ThrowAsync<>()
FluentAssertions Comuns
result.Should().Be(expected);
result.Should().NotBeNull();
result.Should().Contain("substring");
result.Should().StartWith("prefix");
result.Should().BeGreaterThan(0);
result.Should().BeInRange(1, 10);
list.Should().HaveCount(3);
list.Should().Contain(x => x.Id == expectedId);
list.Should().BeEmpty();
await action.Should().ThrowAsync<InvalidOperationException>()
.WithMessage("Error message");
Moq — Mocking
_mock.Setup(x => x.GetByIdAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()))
.ReturnsAsync(user);
_mock.Setup(x => x.GetByIdAsync(specificId, It.IsAny<CancellationToken>()))
.ReturnsAsync(user);
_mock.Setup(x => x.CreateAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()))
.ThrowsAsync(new InvalidOperationException("Error"));
_mock.Verify(x => x.CreateAsync(It.IsAny<User>(), It.IsAny<CancellationToken>()), Times.Once);
_mock.Verify(x => x.DeleteAsync(It.IsAny<Guid>(), It.IsAny<CancellationToken>()), Times.Never);
Estrutura de Pastas
<Projeto>.Tests.Unit/
Application/
UseCases/
User/
CreateUserUseCaseTests.cs
Validators/
User/
CreateUserInputValidatorTests.cs
Domain/
Entities/
UserTests.cs
BDD com SpecFlow (Opcional)
Para testes baseados em comportamento (Gherkin):
dotnet add package SpecFlow.xUnit
dotnet add package SpecFlow.Tools.MsBuild.Generation
Feature (CreateUser.feature)
Feature: Create User
As an API consumer
I want to create a new user
So that they can access the system
Scenario: Create user successfully
Given I have valid user data
When I submit the create user request
Then the user should be created
And the response should contain the user ID
Scenario: Create user with existing email
Given a user with email "test@test.com" already exists
When I submit the create user request with email "test@test.com"
Then I should receive an error "Email já está em uso."
Steps (CreateUser.steps.cs)
[Binding]
public class CreateUserSteps(TestContext context)
{
[Given("I have valid user data")]
public void GivenIHaveValidUserData()
{
context.Input = new CreateUserInput("test@test.com", "Test User");
}
[When("I submit the create user request")]
public async Task WhenISubmitTheCreateUserRequest()
{
context.Result = await context.UseCase.ExecuteAsync(context.Input);
}
[Then("the user should be created")]
public void ThenTheUserShouldBeCreated()
{
context.Result.Should().NotBeNull();
}
}
Cobertura de Código
dotnet test --collect:"XPlat Code Coverage"
dotnet tool install -g dotnet-reportgenerator-globaltool
reportgenerator -reports:"**\coverage.cobertura.xml" -targetdir:"coveragereport" -reporttypes:Html
start coveragereport\index.html
Meta de cobertura:
- UseCases: 90%+
- Domain: 85%+
- Validators: 90%+
- Infra: 70%+
Build e Validação
dotnet test
dotnet build --configuration Release
dotnet test --configuration Release --no-build --verbosity normal
Referências