Pular para o conteúdo principal

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

RegraDescrição
Um ActApenas uma ação principal por teste
Múltiplos Asserts OKDesde que validem o mesmo resultado (ex.: HTTP + corpo + banco)
Sem lógica no AssertEvite if/for complexos; use [Theory] para variações
Arrange explícitoSeed, 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

MockarNão mockar
IParametroService, APIs externas, clockDbContext, SaveChanges (em integração)
ICloudTenantCacheService (modo cloud)Lógica do próprio SUT
ILoggerEntidades EF que você quer persistir

Sintaxe padrão

_parametroService
.ConsultarValorParametro<int>("ATACAREJO_HABILITADO", lojaId)
.Returns(0);

Regra

Nunca mockar SaveChangesAsync e 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 usa DateTimeServer() ou DateTime.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ãoPor que evitar
Assert só HTTP 200API retornava sucesso com saldo errado
Assert só MovEstoqueMovimento funcionava; es1 não
Mock de DbContext em integraçãoNão é integração
EF InMemory para fluxo com transaçãoNão detecta tracking/contexto errado
Teste que depende de ordem de execuçãoFlaky no CI
[Fact] sem cleanup de dadosPolui banco e quebra outros testes
Verificar Received() em tudoTeste acoplado à implementação
Copiar credenciais de produçãoRisco de segurança

10. Cobertura (metas sugeridas)

ÁreaMeta inicial
Rotas SuperApp críticas (integração)1+ teste happy path + 1+ erro por rota
Services com mutação de BDIntegração obrigatória
Helpers / calculadorasUnitário 80%+
Controllers finosIntegraçã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.