Padrões e convenções de testes — SGApi
Este documento define o padrão obrigatório para todos os testes do projeto. Unitários, integração e E2E devem seguir estas regras.
1. Nomenclatura de testes
Formato padrão
MetodoOuRota_Cenario_ResultadoEsperado
Exemplos
// Unitário
CalcularAjuste_QuandoSaldoMenorQueAlvo_RetornaTipoEntradaComDeltaCorreto
CnpjHelper_Normalizar_QuandoComMascara_RemoveCaracteres
// Integração
PostAjustesEstoques_QuandoQuantidadeValida_AtualizaEs1EstoquesMovimentoEHistorico
PostAjustesEstoques_SemPermissaoEstoque_Retorna403
// E2E
SuperApp_AjusteEstoque_FluxoCompleto_RefleteSaldoNoSgLinear
Convenções adicionais
- Use português nos nomes (alinhado ao domínio do projeto) ou inglês de forma consistente em todo o arquivo — não misture no mesmo projeto.
- Evite nomes genéricos:
Test1,DeveFuncionar,ValidarService. - O nome deve permitir entender o que falhou sem abrir o código.
2. Padrão AAA (Arrange — Act — Assert)
Todo teste deve ter três blocos visíveis, separados por linha em branco ou comentários.
[Fact]
public async Task PostAjustesEstoques_QuandoProdutoNaoExiste_RetornaBadRequestComErro()
{
// Arrange
var client = _factory.CreateAuthenticatedClient(permissoes: ["estoque"]);
var payload = AjusteEstoquePayloadBuilder.Novo()
.ComProdutoId(999999)
.Build();
// Act
var response = await client.PostAsJsonAsync("/produtos-gestao/ajustes-estoques", payload);
// Assert
response.StatusCode.Should().Be(HttpStatusCode.BadRequest);
var body = await response.Content.ReadFromJsonAsync<AjusteEstoqueResponseDto>();
body!.Errors.Should().Contain(e => e.Contains("não encontrado"));
}
Regras AAA
| Regra | Descrição |
|---|---|
| Um Act | Apenas uma ação principal por teste |
| Múltiplos Asserts OK | Desde que validem o mesmo resultado (ex.: HTTP + corpo + banco) |
| Sem lógica no Assert | Evite if/for complexos; use [Theory] para variações |
| Arrange explícito | Seed, mocks e payload devem estar claros no início |
3. Testes parametrizados ([Theory])
Use para variações da mesma regra sem duplicar métodos.
[Theory]
[InlineData(1, 3, "E", 2)]
[InlineData(5, 2, "S", 3)]
[InlineData(4, 4, "", 0)]
public void CalcularAjusteEstoque(decimal saldoAtual, decimal quantidadeAlvo, string tipoEsperado, decimal deltaEsperado)
{
var (tipo, delta, _, _) = AjusteEstoqueCalculator.Calcular(saldoAtual, quantidadeAlvo);
tipo.Should().Be(tipoEsperado);
delta.Should().Be(deltaEsperado);
}
4. Assertions
Biblioteca padrão
Preferir FluentAssertions 7 (versão gratuita) ou Shouldly.
// ✅ Legível
resultado.Errors.Should().BeEmpty();
produto.EstoqueAtual.Should().Be(3m);
// ⚠️ Aceitável para casos triviais
Assert.Empty(resultado.Errors);
Mensagens customizadas
produto.EstoqueAtual.Should().Be(3m, "es1.es2_qatu deve refletir o ajuste do SuperApp");
5. Mocks (NSubstitute)
Quando mockar
| Mockar | Não mockar |
|---|---|
IParametroService, APIs externas, clock | DbContext, SaveChanges (em integração) |
ICloudTenantCacheService (modo cloud) | Lógica do próprio SUT |
ILogger | Entidades EF que você quer persistir |
Sintaxe padrão
_parametroService
.ConsultarValorParametro<int>("ATACAREJO_HABILITADO", lojaId)
.Returns(0);
Regra
Nunca mockar
SaveChangesAsynce considerar o teste concluído. Isso reproduz o falso positivo do bug do ajuste de estoque.
6. Isolamento e determinismo
- Cada teste deve rodar independente de ordem e de outros testes.
- Proibido depender de dados “que já existem” no banco local do dev.
- Use seed explícito (integração) ou objetos in-memory (unitário).
- Injete ou congele tempo (
DateTime) quando o código usaDateTimeServer()ouDateTime.Now. - Integração com banco: reset entre testes (Respawn ou transação rollback).
7. Organização de arquivos
Comercial/
AjustesEstoque/
AjusteEstoqueCalculatorTests.cs ← unitário
AjusteEstoqueServiceTests.cs ← unitário (se houver lógica mockada)
AjustesEstoqueIntegrationTests.cs ← integração
Helpers/
AjusteEstoquePayloadBuilder.cs
JwtTestTokenGenerator.cs
Fixtures/
SgApiWebApplicationFactory.cs
MySqlContainerFixture.cs
- 1 classe de teste ≈ 1 classe/rota de produção.
- Builders para payloads complexos (evitar JSON inline de 30 linhas).
8. Checklist antes do merge
Todo PR com testes deve passar:
- Nome segue
Metodo_Cenario_Resultado? - AAA visível?
- Um cenário por teste?
- Determinístico (sem rede/clock/banco externo não controlado)?
- Asserta comportamento observável (não implementação interna)?
- Mutação de dados: assert em todas as tabelas de negócio?
- Sem
Thread.Sleep/ retry manual? - Teste falha quando a regra está errada (validou que o teste “pega” o bug)?
9. Anti-padrões proibidos
| Anti-padrão | Por que evitar |
|---|---|
| Assert só HTTP 200 | API retornava sucesso com saldo errado |
Assert só MovEstoque | Movimento funcionava; es1 não |
Mock de DbContext em integração | Não é integração |
| EF InMemory para fluxo com transação | Não detecta tracking/contexto errado |
| Teste que depende de ordem de execução | Flaky no CI |
[Fact] sem cleanup de dados | Polui banco e quebra outros testes |
Verificar Received() em tudo | Teste acoplado à implementação |
| Copiar credenciais de produção | Risco de segurança |
10. Cobertura (metas sugeridas)
| Área | Meta inicial |
|---|---|
| Rotas SuperApp críticas (integração) | 1+ teste happy path + 1+ erro por rota |
| Services com mutação de BD | Integração obrigatória |
| Helpers / calculadoras | Unitário 80%+ |
| Controllers finos | Integração (não unitário isolado) |
Cobertura de linha não substitui assert de comportamento. 100% de cobertura com mocks fracos ainda deixa bugs passarem.