Pular para o conteúdo principal

Setup e infraestrutura de testes — SGApi

Guia para criar e configurar os projetos de teste, CI e fixtures compartilhadas.


1. Projetos NuGet

SGApi.UnitTests.csproj

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
<PackageReference Include="NSubstitute" Version="5.*" />
<PackageReference Include="FluentAssertions" Version="7.*" />
<PackageReference Include="coverlet.collector" Version="6.*" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SGApi Solution\SGApi.csproj" />
</ItemGroup>
</Project>

SGApi.IntegrationTests.csproj

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.*" />
<PackageReference Include="xunit" Version="2.*" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.*" />
<PackageReference Include="FluentAssertions" Version="7.*" />
<PackageReference Include="coverlet.collector" Version="6.*" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Testing" Version="10.*" />
<PackageReference Include="Testcontainers.MySql" Version="4.*" />
<PackageReference Include="Respawn" Version="6.*" />
<PackageReference Include="Bogus" Version="35.*" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\SGApi Solution\SGApi.csproj" />
</ItemGroup>
</Project>

Ajuste versões conforme central package management do repositório, se existir.


2. Expor Program para integração

O Program.cs da SGApi usa top-level statements. Adicione no final do Program.cs ou em arquivo Program.IntegrationTests.cs:

public partial class Program { }

Sem isso, WebApplicationFactory<Program> não compila.


3. Ambiente Testing

Crie perfil lógico para desabilitar side effects:

ComponenteEm testes
Serilog → SeqDesabilitar ou sink console only
InitializeCloudCacheAsyncSkip ou mock
Migrations em backgroundControlar manualmente no fixture
Args --cloudForçar modo on-prem via factory

Na factory:

builder.UseEnvironment("Testing");

No código de produção, onde necessário:

if (app.Environment.IsEnvironment("Testing"))
{
// skip Seq, skip cloud init, etc.
}

Preferir substituir serviços via ConfigureTestServices em vez de espalhar if (Testing) — use if só quando inevitável.


4. WebApplicationFactory base

public class SgApiWebApplicationFactory : WebApplicationFactory<Program>, IAsyncLifetime
{
private readonly MySqlContainer _mysql = new MySqlBuilder()
.WithImage("mysql:8.0")
.Build();

public string ConnectionString => _mysql.GetConnectionString();

protected override void ConfigureWebHost(IWebHostBuilder builder)
{
builder.UseEnvironment("Testing");

// Args equivalentes ao modo local on-prem
builder.UseSetting("Database:Server", _mysql.Hostname);
builder.UseSetting("Database:Port", _mysql.GetMappedPublicPort(3306).ToString());
// ... conforme DadosInicializacaoModel / ConnectionStringProvider

builder.ConfigureTestServices(services =>
{
// Substituir ICloudTenantCacheService se necessário
// JWT secret fixo para testes
});
}

public async Task InitializeAsync() => await _mysql.StartAsync();

public new async Task DisposeAsync()
{
await _mysql.DisposeAsync();
await base.DisposeAsync();
}
}

Pontos críticos SGApi

  1. ConfigureTestServices, não ConfigureServices — senão o DI de produção sobrescreve os mocks.
  2. Connection string dinâmica do Testcontainers — nunca apontar para BD de dev/prod.
  3. AddDatabaseContexts(dadosArgs) — passar args de teste compatíveis com ConnectionStringProvider.
  4. Schema legado — aplicar scripts de MigrationScripts ou dump SQL mínimo no InitializeAsync.

5. Collection Fixture (performance)

Um container MySQL por suite, não por teste:

[CollectionDefinition("Integration")]
public class IntegrationCollection : ICollectionFixture<SgApiWebApplicationFactory> { }

[Collection("Integration")]
public class AjustesEstoqueIntegrationTests
{
private readonly SgApiWebApplicationFactory _factory;
public AjustesEstoqueIntegrationTests(SgApiWebApplicationFactory factory) => _factory = factory;
}

Desabilitar paralelismo na collection:

[assembly: CollectionBehavior(DisableTestParallelization = true)]

Ou marcar apenas a collection de integração com fixture compartilhada e rodar integração em job CI separado.


6. Reset de banco (Respawn)

public class DatabaseResetHelper
{
private Respawner _respawner = null!;

public async Task InitializeAsync(string connectionString)
{
await using var conn = new MySqlConnection(connectionString);
await conn.OpenAsync();

_respawner = await Respawner.CreateAsync(conn, new RespawnerOptions
{
DbAdapter = DbAdapter.MySql,
TablesToIgnore = new[] { "__EFMigrationsHistory" }
});
}

public async Task ResetAsync(string connectionString)
{
await using var conn = new MySqlConnection(connectionString);
await conn.OpenAsync();
await _respawner.ResetAsync(conn);
}
}

Chamar ResetAsync no início de cada teste (ou [ IAsyncLifetime ] na classe base).


7. JWT e permissões de teste

Helper para client autenticado:

public static class HttpClientExtensions
{
public static HttpClient CreateAuthenticatedClient(
this WebApplicationFactory<Program> factory,
int usuarioId = 1,
string[]? permissoes = null)
{
var client = factory.CreateClient();
var token = JwtTestTokenGenerator.Gerar(usuarioId, permissoes ?? []);
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
return client;
}
}

Claims devem refletir o que AuthorizePermission e SessaoTokenValidator exigem (incluir sessao_id se validação de sessão estiver ativa — mockar validador ou seed em superapp_usuarios).


8. Seed de dados

Centralize em DatabaseSeeder:

public static class DatabaseSeeder
{
public static async Task SeedAjusteEstoqueAsync(SglinxDbContext db)
{
// empresa, produto es1, estoques, permissões, mov table do mês, etc.
}
}

Regras:

  • IDs fixos nos testes (produtoId = 33610, lojaId = 2) para asserts previsíveis.
  • Seed mínimo por teste ou por classe — evitar dump completo do cliente.
  • Tabelas dinâmicas mov{empresa}estoque{MMYY}: criar no seed ou via EstruturaMovEstoque equivalente.

9. Executar localmente

# Unitários (rápido)
dotnet test SGApi.UnitTests

# Integração (requer Docker)
dotnet test SGApi.IntegrationTests

# Com cobertura
dotnet test SGApi.UnitTests --collect:"XPlat Code Coverage"

10. CI/CD

Implementado em .github/workflows/ via reusable-test.yml:

  1. dotnet build SGApi.sln -c Release
  2. dotnet test tests/SGApi.ArchitectureTests -c Debug
  3. dotnet test tests/SGApi.UnitTests -c Debug
  4. SGAPI_MYSQL_IMAGE=mysql:8.0 e mysql:5.5tests/SGApi.IntegrationTests

E2E bloqueante (PR staging → main): tests/SGApi.E2ETests com secrets STAGING_*.

Configuração de secrets, branch protection e GHCR: .github/README.md.

Local: scripts/test.ps1.


11. Próximos passos

  1. Criar os dois projetos de teste na solution.
  2. Adicionar public partial class Program.
  3. Implementar SgApiWebApplicationFactory + seed mínimo.
  4. Primeiro teste: testes de integração — ajuste de estoque.