Por que esta aula existe
O MVP que cada grupo está construindo para o parceiro de mercado não é um monolito isolado. Ele é, na prática, um pequeno ecossistema de serviços: a API .NET conversa com um banco de dados, talvez com um cache, talvez com uma fila, quase certamente com uma ou duas APIs externas — do parceiro, de um provedor de autenticação, de um gateway de pagamento, de outro squad. Essa arquitetura é o que chamamos de SOA (Service-Oriented Architecture).
Numa arquitetura SOA, a maioria dos bugs caros não aparece quando você testa uma classe isolada. Eles aparecem quando duas peças tentam conversar pela primeira vez: o ORM gera um SQL que o Postgres real recusa; o JSON que vem da API externa tem um campo faltando; o token JWT expira no meio da chamada; o broker de mensagens devolve um erro de schema. Tudo isso é integração.
Em SOA, testar só com mocks é como dirigir só no estacionamento: você nunca encontra os buracos da rua. Bugs de integração custam ~10× o que custa um bug unitário, porque eles só aparecem quando várias camadas conversam — e isolar a causa exige rodar tudo junto.
Esta aula te dá duas ferramentas concretas para atacar esse problema no .NET, sem cair na armadilha do "rodar contra produção" nem na do "mocko tudo e finjo que funciona":
- Testcontainers — para dependências que você possui (BD, broker, cache do seu serviço). Sobe a coisa real em container Docker descartável.
- VCR / WireMock.Net — para dependências que você consome (APIs de outros squads, terceiros). Grava cassetes HTTP e reproduz determinístico.
E te dá também uma conversa honesta sobre os limites de cada uma. Especialmente: por que VCR não substitui contract testing de verdade (que é problema da aula 9, e cuja stack canônica é Pact, não WireMock).
- Olhar para uma dependência do seu MVP e classificar em 5 segundos: possuída ou consumida.
- Subir um Postgres real num teste C# e validar uma query contra o dialeto real, sem precisar de banco compartilhado.
- Gravar a interação com uma API externa numa primeira execução, e rodar offline para sempre depois disso.
- Defender em entrevista técnica por que VCR ≠ Pact sem se enrolar.
- Entregar a ponderada em 90 minutos com rastreabilidade RF + RNF correta.
O que é teste de integração
Antes de qualquer ferramenta, precisamos de uma definição operacional clara. A literatura é cheia de variações sutis. Vamos adotar uma e segurá-la firme durante o módulo inteiro:
Um teste de integração exercita dois ou mais componentes do sistema conversando entre si — controller↔service↔repository, app↔banco, app↔serviço externo — para validar que o wiring funciona, não apenas a lógica isolada de cada peça.
Compare com as outras duas categorias da pirâmide:
Os três níveis em uma frase cada
| Nível | Pergunta que responde | Tempo de execução | Quem você confia? |
|---|---|---|---|
| Unitário | "Esta peça faz o que prometeu?" | Milissegundos | Só na peça testada. Tudo ao redor é mock. |
| Integração | "Estas peças combinam direito?" | Segundos | Em várias peças + dependências reais ou semi-reais. |
| End-to-end (E2E) | "O usuário consegue chegar lá?" | Minutos | No sistema inteiro: navegador real + back-end real + BD real. |
O que SÓ teste de integração captura
Existe uma classe inteira de defeitos que nunca aparece em teste unitário, porque ele depende de várias peças tentando conversar. Os principais:
- Wiring entre camadas: controller chama service que chama repository. Cada peça passa em unit isolado, mas a configuração de DI no
Startup.cs/Program.csestá errada — o serviço não está registrado, está registrado comoScopednum lugar que precisa deSingleton, ou o middleware de autenticação está antes do middleware de roteamento. Você só descobre quando uma request HTTP de verdade tenta passar pelo pipeline inteiro. - Contratos com o BD real: o EF Core gera SQL diferente de mocks. Migrations, índices, tipos NUMERIC × DECIMAL, colunas
NOT NULLque viraramNULLsem ninguém perceber, JSONB que o SQLite InMemory simplesmente não suporta — tudo isso só aparece quando uma query roda contra um banco de verdade. - Auth, cache, middleware: JWT, claims, políticas de autorização, headers de cache, CORS. Todo esse aparato só executa quando o pipeline HTTP roda inteiro — algo que unit tests pulam por completo.
- Mapeamento ORM: relações, lazy loading, navigation properties, herança TPH/TPT. Configuração errada do
Fluent APIsó explode quando uma query real tenta materializar o resultado. - Integração com serviço externo: em SOA, sua app chama a API de outro squad. Headers obrigatórios, timeouts, política de retry, contratos de payload, esquemas de erro — só aparecem em integração, nunca em mocks isolados que você mesmo escreveu.
Boehm e o movimento "shift left" deixaram uma regra empírica: cada nível para cima na pirâmide multiplica o custo de detectar e corrigir um bug por ~10×. Bug unitário custa ~1 unidade. Bug pego em integração: ~10. Bug pego em E2E: ~100. Bug pego em produção pelo usuário: ~1000. A faixa amarela da pirâmide (integração) é, em termos de relação custo-benefício, a melhor janela para investir testes em SOA.
A pirâmide de testes
A pirâmide de testes é o diagrama mais famoso de toda a engenharia de qualidade. Foi popularizado por Mike Cohn em Succeeding with Agile e refinado por Martin Fowler. A ideia: base larga (muitos testes baratos), topo estreito (poucos testes caros).
Proporções saudáveis (rule of thumb)
Não há regra rígida, mas como orientação inicial:
- 70% unitários — base larga, garantem que cada peça funciona isolada.
- ~25% integração — meio, garantem que as peças conversam.
- ~5% E2E — topo, só fluxos críticos de usuário (login → checkout, criação de conta, fluxo principal do MVP).
O anti-padrão: pirâmide invertida (taça de sorvete)
Times que começam pelos testes E2E geralmente entram em um anti-padrão clássico: poucos unitários, alguns integração, muitos E2E. Aparece visualmente como uma taça de sorvete (invertida). Por que é ruim?
- E2E são lentos: 30s a vários minutos cada. CI de 1h+. Time perde a vontade de rodar.
- E2E são frágeis: qualquer mudança de CSS, qualquer microssegundo a mais de latência, qualquer dado de teste fora do lugar quebra. Vira ruído.
- E2E são caros de debugar: quando quebram, você tem 10 camadas para investigar. Em unit, você tem 1.
Conte os testes por nível. Se a contagem de integração + E2E é maior que a de unitários, você tem uma taça de sorvete. Se você nunca rodou um teste de integração no MVP do grupo, está pulando o nível que mais paga em arquitetura SOA.
Black-box: contrato como observável
Quando você escreve um teste de integração, tem uma escolha fundamental: vai espiar dentro do sistema (white-box) ou só observar o que entra e sai (black-box)? Em SOA, a resposta correta é quase sempre black-box — e este capítulo explica por quê.
As duas perspectivas
| Aspecto | ⬛ Black-box | ⬜ White-box |
|---|---|---|
| O que vê | Apenas entradas e saídas observáveis (request, response, side effects visíveis). | Caminhos internos: cobertura de linhas, branches, métodos privados, estado interno. |
| O que ignora | Implementação interna — código pode ser refatorado livremente. | Nada. Acopla-se ao "como" da implementação. |
| Sobrevive a refactor? | Sim Se contrato igual, teste continua verde. | Não Mudou o caminho? Teste quebra mesmo se contrato continua igual. |
| Bom para… | Integração, contrato, API pública. | Unitário de lógica complexa (algoritmo, regra de cálculo). |
O que é "contrato" em SOA
Em arquitetura orientada a serviços, o contrato é o que o outro lado consegue ver. Para um endpoint HTTP, isso significa:
- O verbo + path (
POST /api/pedidos); - O schema do payload de entrada (campos obrigatórios, tipos, formatos);
- O schema do payload de saída (estrutura, headers, content-type);
- Os códigos de status HTTP possíveis e o que cada um significa (200, 201, 400, 401, 422, 503…);
- Os side effects observáveis: uma mensagem publicada num broker, um arquivo gravado, um e-mail disparado.
Essa é a superfície pública do seu serviço. É o que outro squad consome. É o que precisa ser estável.
Quando você testa POST /pedidos só pela request e pela response, você está protegendo o contrato. Você pode trocar o ORM, reescrever o algoritmo, dividir a service em duas — enquanto o contrato observável continua igual, o teste continua verde. Esse é o ângulo certo em SOA.
Sinais de que você está testando white-box sem querer
Olhe seus testes de integração existentes (ou os primeiros que vai escrever) e verifique se aparecem esses padrões. Se sim, refatore para black-box:
- Você usa reflection para acessar campo privado e checar valor.
- Você expõe um método como
internalsó para o teste poder chamar. - Você abre o
DbContextdiretamente no teste para checar se um registro foi gravado, em vez de pedir via endpointGET. - O teste quebra quando você renomeia uma classe ou move um arquivo, sem ter mudado nada visível externamente.
- O teste assert algo como "este método foi chamado X vezes" em vez de "esta saída é Y".
Em teste de integração, não inspecione estado interno. Se precisar abrir o objeto pra checar um campo privado, refatore o teste — não a regra de negócio. O teste deve ler como a documentação executável do contrato: "se eu mandar X, o serviço promete Y".
O mapa SOA: dependências possuídas × consumidas
Este capítulo é o coração da aula. Se você sair daqui dominando apenas uma coisa, que seja esta: antes de escrever qualquer teste de integração em SOA, classifique cada dependência em uma das duas colunas abaixo. A ferramenta de teste sai do mapa, não da preferência pessoal.
🏠 Dependências POSSUÍDAS
São suas. Você controla schema, deploy, versão. Você pode subir uma cópia idêntica em qualquer máquina — local, CI, staging.
- Banco de dados do seu serviço (PostgreSQL, SQL Server, MySQL)
- Cache que você gerencia (Redis local)
- Message broker que você administra (RabbitMQ, Kafka)
- Storage que sua equipe configura (MinIO, S3 local)
- Serviços auxiliares do mesmo squad
distintos
🌐 Dependências CONSUMIDAS
São de outro squad ou de terceiros. Você só conhece pela API HTTP. Não controla deploy, não controla uptime, não controla quando o contrato muda.
- API REST de outro squad (catálogo, pagamentos, autenticação interna)
- Serviço do parceiro de mercado (ERP, CRM, sistema legado)
- Provider de autenticação (Auth0, Cognito, Keycloak hospedado)
- Gateway de e-mail/SMS (SendGrid, Twilio, AWS SES)
- Qualquer API externa pública (CEP, câmbio, weather, etc.)
Como classificar na prática
Quando bater dúvida sobre uma dependência específica, faça-se 3 perguntas:
- Quem controla o deploy? Se é o seu time → possuída. Se é outro time ou terceiro → consumida.
- Posso subir uma cópia idêntica numa máquina nova em 5 minutos? Se sim → possuída. Se não (precisa de credenciais externas, contratos comerciais, infra de outro time) → consumida.
- Quando o schema muda, eu fico sabendo antes ou depois? Antes (eu controlo a migration) → possuída. Depois (descubro porque quebrou) → consumida.
Exemplo prático: anatomia de um MVP típico
Imagine que seu grupo está construindo um serviço de recomendações para o parceiro. Vamos mapear as dependências:
| Dependência | Tipo | Por quê | Ferramenta de teste |
|---|---|---|---|
| PostgreSQL do serviço de recomendações | Possuída | Você cria as migrations, você define o schema, você controla a versão. | Testcontainers |
| Redis para cache de scores | Possuída | Você sobe a instância, você define a estratégia de TTL. | Testcontainers |
| API de catálogo do parceiro | Consumida | Você só consome — não controla schema, deploy nem versão. | WireMock.Net |
| API de autenticação OAuth2 do parceiro | Consumida | Mesma lógica — é um serviço externo do qual você só vê a interface HTTP. | WireMock.Net |
| RabbitMQ para publicar eventos | Possuída | Seu time configura o broker e os exchanges/queues. | Testcontainers |
| API pública de tradução (DeepL) | Consumida | Terceiro externo. Sujeito a rate limit, billing, uptime que você não controla. | WireMock.Net |
Em SOA, a primeira pergunta a fazer sobre cada dependência é: "eu possuo ou eu consumo?". A resposta define a ferramenta de teste. Possuída → container Docker descartável. Consumida → cassete HTTP gravado. Os dois problemas são diferentes e exigem ferramentas diferentes.
O exercício recomendado antes de começar
Pegue uma folha de papel (ou um README do projeto) e desenhe duas colunas: possuídas e consumidas. Liste todas as dependências do MVP do seu grupo. Esse é o mapa de testes de integração do projeto. A partir dele, você consegue planejar quais testes escrever e qual ferramenta usar para cada um.
Testcontainers · passo a passo
O Testcontainers for .NET é uma biblioteca que sobe containers Docker descartáveis dentro do ciclo de vida do teste. PostgreSQL real, Redis real, RabbitMQ real — não um mock, não um in-memory, não uma versão simplificada. O processo de verdade, com o dialeto de verdade, com o protocolo de verdade.
6.1 · Quando usar (e quando não)
- Você quer rodar contra o mesmo dialeto que está em produção (PostgreSQL com JSONB, SQL Server com geographic types, MySQL com character sets específicos).
- Sua aplicação depende de features que
SQLite InMemorynão suporta:SELECT FOR UPDATE, full-text search, índices GIN, particionamento, transações isoladas. - Você precisa testar integração com Redis, RabbitMQ, Kafka, MinIO ou outros serviços que rodam em container.
- Você quer testar migrations contra um banco vazio recém-criado.
- A máquina do desenvolvedor não pode rodar Docker (algumas empresas/laboratórios proíbem). Sem Docker, sem Testcontainers — verifique antes.
- O runner do seu CI não tem Docker disponível (alguns planos free de CI não têm). Verifique antes de adotar como padrão do time.
- Você só precisa testar lógica pura que não toca em BD nem em serviços externos. Aí é teste unitário.
6.2 · Pré-requisitos
Antes de começar, garanta que estes itens estão prontos:
- .NET SDK 8.0+ instalado (
dotnet --versiondeve responder). - Docker Desktop rodando (no Windows/Mac) ou Docker Engine ativo no Linux (
docker psdeve listar containers). - Conexão com a internet na primeira execução, para baixar a imagem do banco.
- O projeto da API já configurado, com pelo menos um
DbContextou repositório implementado.
6.3 · Passo a passo · configurando o projeto
-
Criar o projeto de testes
No diretório raiz do projeto, ao lado da pasta da API, crie um projeto de testes xUnit:
terminalbash# do diretório raiz da solution dotnet new xunit -n Pedidos.IntegrationTests dotnet sln add Pedidos.IntegrationTests/Pedidos.IntegrationTests.csproj # adicionar referência ao projeto da API dotnet add Pedidos.IntegrationTests reference Pedidos.Api
-
Instalar os pacotes NuGet necessários
Você precisa do core de Testcontainers + o módulo específico para o seu banco. Aqui usamos PostgreSQL como exemplo:
terminalbashcd Pedidos.IntegrationTests dotnet add package Testcontainers dotnet add package Testcontainers.PostgreSql dotnet add package Microsoft.AspNetCore.Mvc.Testing dotnet add package Microsoft.EntityFrameworkCore.Design # opcional, mas recomendado: dotnet add package Respawn dotnet add package BogusResumo do que cada um faz:
Testcontainers— núcleo da biblioteca.Testcontainers.PostgreSql— builder específico do Postgres (existem módulos para MsSql, MySql, MongoDB, Redis, RabbitMQ, Kafka, etc.).Microsoft.AspNetCore.Mvc.Testing— forneceWebApplicationFactoryse você quiser testar via HTTP.Respawn— limpeza rápida do banco entre testes.Bogus— gerador de dados faker com seed determinístico.
-
Criar a fixture do container
A fixture é a classe que gerencia o ciclo de vida do container. Implementa
IAsyncLifetimepara que o xUnit chameInitializeAsyncantes da suite eDisposeAsyncao final.Pedidos.IntegrationTests/Fixtures/PostgresFixture.csC#using Testcontainers.PostgreSql; using Xunit; namespace Pedidos.IntegrationTests.Fixtures; public class PostgresFixture : IAsyncLifetime { private readonly PostgreSqlContainer _db = new PostgreSqlBuilder() .WithImage("postgres:16-alpine") .WithDatabase("pedidos_test") .WithUsername("app") .WithPassword("app") .WithCleanUp(true) .Build(); public string ConnectionString => _db.GetConnectionString(); public async Task InitializeAsync() { await _db.StartAsync(); // rodar migrations contra o BD recém-criado await using var ctx = new AppDbContext( new DbContextOptionsBuilder<AppDbContext>() .UseNpgsql(ConnectionString).Options); await ctx.Database.MigrateAsync(); } public Task DisposeAsync() => _db.DisposeAsync().AsTask(); }
-
Marcar a fixture para reuso na collection
Se múltiplas classes de teste precisam compartilhar o mesmo container (recomendado, porque startup do container é caro), defina uma collection:
Pedidos.IntegrationTests/Fixtures/DatabaseCollection.csC#using Xunit; namespace Pedidos.IntegrationTests.Fixtures; [CollectionDefinition("database")] public class DatabaseCollection : ICollectionFixture<PostgresFixture> { }
-
Escrever o teste de sucesso (black-box)
Aqui vai um teste integrado que valida o fluxo de criar pedido + recuperar pedido — só pelo contrato observável:
Pedidos.IntegrationTests/PedidosFlowTests.csC#using Pedidos.IntegrationTests.Fixtures; using Xunit; namespace Pedidos.IntegrationTests; [Collection("database")] public class PedidosFlowTests { private readonly PostgresFixture _fix; public PedidosFlowTests(PostgresFixture fix) => _fix = fix; [Fact] public async Task DeveCriarPedidoEListarComItens() { // Arrange — Postgres REAL rodando, schema migrado await using var ctx = new AppDbContext( new DbContextOptionsBuilder<AppDbContext>() .UseNpgsql(_fix.ConnectionString).Options); var svc = new PedidoService(ctx); var dto = new NovoPedidoDto(ClienteId: 42, Itens: [new(1, 2), new(3, 1)]); // Act — passa pelo wiring real + query real var pedidoId = await svc.CriarAsync(dto); var achado = await svc.ObterAsync(pedidoId); // Assert — só pelo contrato observável Assert.NotNull(achado); Assert.Equal(42, achado!.ClienteId); Assert.Equal(2, achado.Itens.Count); } }
-
Escrever o teste de erro / exceção
Todo fluxo importante precisa de um teste de cenário ruim. Aqui testamos que pedido com cliente inexistente lança a exceção esperada — sem inspecionar estado interno:
Pedidos.IntegrationTests/PedidosFlowTests.cs (cont.)C#[Fact] public async Task DeveRecusarPedidoComClienteInexistente() { await using var ctx = new AppDbContext( new DbContextOptionsBuilder<AppDbContext>() .UseNpgsql(_fix.ConnectionString).Options); var svc = new PedidoService(ctx); var dto = new NovoPedidoDto( ClienteId: 99999, // não existe Itens: [new(1, 1)]); var ex = await Assert.ThrowsAsync<ClienteNaoEncontradoException>( () => svc.CriarAsync(dto)); Assert.Contains("99999", ex.Message); }
-
Garantir isolamento entre testes (Respawn)
Se múltiplos testes escrevem no banco, eles podem se sujar mutuamente. Use
Respawnpara "voltar ao estado inicial" rápido antes de cada teste, sem dropar e recriar o banco:Pedidos.IntegrationTests/Fixtures/PostgresFixture.cs (adição)C#using Respawn; using Npgsql; // dentro da classe PostgresFixture, adicione: private Respawner? _respawner; public async Task ResetAsync() { await using var conn = new NpgsqlConnection(ConnectionString); await conn.OpenAsync(); _respawner ??= await Respawner.CreateAsync(conn, new RespawnerOptions { DbAdapter = DbAdapter.Postgres }); await _respawner.ResetAsync(conn); }
E em cada classe de teste:
usoC#public PedidosFlowTests(PostgresFixture fix) { _fix = fix; _fix.ResetAsync().GetAwaiter().GetResult(); }
-
Rodar localmente
Tudo configurado, rode os testes:
terminalbashdotnet test Pedidos.IntegrationTests
Na primeira execução, o Docker baixa a imagem (~80 MB para Postgres alpine). Demora um pouco. Da segunda em diante, startup do container leva ~3–5 segundos. Cada teste em si roda em milissegundos depois do container estar de pé.
6.4 · Configurando no CI
GitLab CI
integration_tests: stage: test image: mcr.microsoft.com/dotnet/sdk:8.0 services: - name: docker:dind alias: docker variables: DOCKER_HOST: "tcp://docker:2375" DOCKER_TLS_CERTDIR: "" script: - dotnet restore - dotnet test Pedidos.IntegrationTests --logger "trx;LogFileName=tests.trx" rules: - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
GitHub Actions
name: tests on: [pull_request] jobs: integration: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: dotnet-version: '8.0.x' # Docker já vem instalado no ubuntu-latest runner - run: dotnet test Pedidos.IntegrationTests
6.5 · Troubleshooting comum
| Sintoma | Causa provável | Solução |
|---|---|---|
Cannot connect to the Docker daemon | Docker Desktop não está rodando. | Inicie o Docker Desktop. Em Linux: systemctl start docker. |
| Testes lentos (mais de 30s por teste) | Cada teste sobe um container novo. | Use IClassFixture ou ICollectionFixture para reusar o container entre testes. |
relation "Pedidos" does not exist | Schema não foi migrado dentro da fixture. | Chame ctx.Database.MigrateAsync() no InitializeAsync. |
| CI fica vermelho mas local passa | Runner sem Docker disponível. | Confirme com o time de DevOps. Em GitLab, configure docker:dind. Em GitHub Actions, use ubuntu-latest. |
| Testes interferem uns nos outros | Banco não está sendo limpo entre testes. | Adicione Respawn ou use TRUNCATE manual no setup de cada classe. |
port is already allocated | Container anterior não foi derrubado. | Garante que DisposeAsync está sendo chamado. Use WithCleanUp(true) no builder. |
WireMock.Net (VCR) · passo a passo
Para dependências consumidas — APIs de outros squads, terceiros, gateways externos — a estratégia é completamente diferente. Você não vai subir o serviço externo num container (você não tem o código dele e não controla o deploy). Você vai gravar a interação HTTP numa primeira execução e reproduzir deterministicamente nas próximas.
7.1 · O conceito do cassete (VCR)
O conceito veio originalmente do VCR.py (Ruby, depois portado para Python). A metáfora é a de uma fita VHS dos anos 90: você grava o programa uma vez e assiste quantas vezes quiser depois, sempre exatamente igual.
- 1ª execução · gravação: o teste roda contra a API real. A biblioteca intercepta o HTTP, grava request + response num arquivo (o cassete, geralmente JSON ou YAML).
- Execuções seguintes · replay: o teste roda contra o stub. O HTTP é interceptado e a resposta vem do cassete. Não chama a API real. Não depende de rede. Não depende de uptime.
Em .NET, a biblioteca padrão de mercado é o WireMock.Net. Ela suporta os dois modos: criar mappings manualmente em código (modo stub) ou gravar via proxy (modo recording).
7.2 · Pré-requisitos
- .NET SDK 8.0+ instalado.
- O projeto da sua API já configurado, com pelo menos uma classe que faz uma chamada HTTP externa via
HttpClient. - A URL da API externa (a real) — para gravar o cassete pela primeira vez.
- Docker NÃO é necessário — WireMock.Net roda em-process.
7.3 · Passo a passo · setup do projeto
-
Instalar o pacote
No projeto de testes:
terminalbashcd Pedidos.IntegrationTests dotnet add package WireMock.Net
-
Criar a fixture do WireMock
Igual ao Testcontainers, usamos
IAsyncLifetimepara gerenciar o ciclo de vida do servidor de stub:Pedidos.IntegrationTests/Fixtures/CatalogoApiFixture.csC#using WireMock.Server; using WireMock.Settings; using Xunit; namespace Pedidos.IntegrationTests.Fixtures; public class CatalogoApiFixture : IAsyncLifetime { public WireMockServer Server { get; private set; } = null!; public string Url => Server.Url!; public Task InitializeAsync() { Server = WireMockServer.Start(new WireMockServerSettings { // porta dinâmica — evita conflito com outras suites Port = 0, ReadStaticMappings = true, StaticMappingsPath = "Mappings/Catalogo" }); return Task.CompletedTask; } public Task DisposeAsync() { Server.Stop(); Server.Dispose(); return Task.CompletedTask; } }
-
Modo manual · criar mappings em código
A forma mais simples (e geralmente recomendada para começar) é definir os stubs em código C#. Você ganha autocomplete, validação de compilador e facilidade de debug:
Pedidos.IntegrationTests/CatalogoTests.csC#using Pedidos.IntegrationTests.Fixtures; using WireMock.RequestBuilders; using WireMock.ResponseBuilders; using Xunit; namespace Pedidos.IntegrationTests; public class CatalogoTests : IClassFixture<CatalogoApiFixture> { private readonly CatalogoApiFixture _fix; public CatalogoTests(CatalogoApiFixture fix) => _fix = fix; [Fact] public async Task DeveRetornarItemQuandoCatalogoResponde200() { // Arrange — define o stub do catálogo _fix.Server .Given(Request.Create() .UsingGet() .WithPath("/catalogo/42")) .RespondWith(Response.Create() .WithStatusCode(200) .WithHeader("Content-Type", "application/json") .WithBodyAsJson(new { id = 42, nome = "Notebook X", estoque = 7 })); var http = new HttpClient { BaseAddress = new Uri(_fix.Url) }; var svc = new CatalogoService(http); // Act var item = await svc.ObterAsync(42); // Assert — black-box Assert.Equal(42, item.Id); Assert.Equal("Notebook X", item.Nome); Assert.Equal(7, item.Estoque); } }
-
Cenário de erro · stub de 503 e timeout
Para testar como o consumer reage a falhas do provider, defina mappings de erro. Aqui simulamos uma indisponibilidade e validamos que o serviço propaga uma exceção tipada:
CatalogoTests.cs (cont.)C#[Fact] public async Task DeveLancarQuandoCatalogoIndisponivel() { _fix.Server .Given(Request.Create().UsingGet().WithPath("/catalogo/99")) .RespondWith(Response.Create() .WithStatusCode(503) .WithBody("service unavailable")); var http = new HttpClient { BaseAddress = new Uri(_fix.Url) }; var svc = new CatalogoService(http); var ex = await Assert.ThrowsAsync<CatalogoIndisponivelException>( () => svc.ObterAsync(99)); Assert.Contains("503", ex.Message); } [Fact] public async Task DeveRespeitarTimeoutDoCatalogo() { _fix.Server .Given(Request.Create().UsingGet().WithPath("/catalogo/lento")) .RespondWith(Response.Create() .WithStatusCode(200) .WithDelay(TimeSpan.FromSeconds(10)) .WithBodyAsJson(new { id = 1 })); var http = new HttpClient { BaseAddress = new Uri(_fix.Url), Timeout = TimeSpan.FromMilliseconds(500) }; var svc = new CatalogoService(http); await Assert.ThrowsAsync<TaskCanceledException>( () => svc.ObterAsync(1)); }
-
Modo recording · gravar cassete da API real
Quando a API externa é complexa e você quer capturar a resposta exata (com headers, datas, formatos), use o modo proxy. Você define a URL real, o servidor faz proxy, e grava o roundtrip em JSON:
Tools/RecordCatalogoCassette.cs (utilitário, roda 1× manual)C#using WireMock.Server; using WireMock.Settings; var server = WireMockServer.Start(new WireMockServerSettings { Port = 8088, ProxyAndRecordSettings = new ProxyAndRecordSettings { Url = "https://api.catalogo-real.com", SaveMapping = true, SaveMappingToFile = true } }); Console.WriteLine("WireMock recording em http://localhost:8088"); Console.WriteLine("Faça as requests que quer gravar e tecle Enter para sair"); Console.ReadLine(); server.Stop();
Depois rode
curl http://localhost:8088/catalogo/42. O WireMock proxia pra API real, retorna a resposta verdadeira e grava o mapping em__admin/mappings/. Mova esses arquivos paraPedidos.IntegrationTests/Mappings/Catalogo/e versione no Git. -
Versionar cassetes no repositório
Os mappings JSON devem ficar versionados junto com o código. Estrutura recomendada:
estrutura de pastastreePedidos.IntegrationTests/ ├── Fixtures/ │ ├── PostgresFixture.cs │ └── CatalogoApiFixture.cs ├── Mappings/ │ └── Catalogo/ │ ├── get-item-42.json │ ├── get-item-not-found.json │ └── post-item-success.json ├── CatalogoTests.cs ├── PedidosFlowTests.cs └── Pedidos.IntegrationTests.csproj
Configure no
.csprojpara copiar os mappings para o output:Pedidos.IntegrationTests.csprojXML<ItemGroup> <None Update="Mappings\**\*.json"> <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> </None> </ItemGroup>
-
Integrar com HttpClient via DI
Para que a sua API real use o WireMock no teste e a URL verdadeira em produção, configure
HttpClientFactorycom um nome lógico no DI:Program.cs (API real)C#builder.Services.AddHttpClient("Catalogo", c => { c.BaseAddress = new Uri(builder.Configuration["Catalogo:BaseUrl"]!); c.Timeout = TimeSpan.FromSeconds(5); });
No teste, sobrescreva a config apontando para o WireMock:
uso no teste com WebApplicationFactoryC#var factory = new WebApplicationFactory<Program>() .WithWebHostBuilder(b => b.ConfigureAppConfiguration((_, cfg) => { cfg.AddInMemoryCollection(new Dictionary<string, string?> { ["Catalogo:BaseUrl"] = _fix.Url // aponta para o stub }); })); var client = factory.CreateClient(); // agora as chamadas internas para "Catalogo" vão para o WireMock
-
Rodar localmente e no CI
WireMock.Net roda em-process: não precisa de Docker. O comando é idêntico ao do Testcontainers:
terminalbashdotnet test Pedidos.IntegrationTests
No CI, qualquer runner com .NET SDK funciona — não precisa de configuração especial:
.github/workflows/test.ymlYAMLjobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-dotnet@v4 with: { dotnet-version: '8.0.x' } - run: dotnet test
7.4 · Troubleshooting comum
| Sintoma | Causa provável | Solução |
|---|---|---|
| Test ignora o stub e chama a URL real | HttpClient não foi apontado para o WireMock. | Verifique BaseAddress. Use HttpClientFactory + config override. |
NoMatchingMapping | Path/query do mapping não bate com a request real. | Use WithPathMatching em vez de WithPath (regex). Ative log: server.LogEntries. |
| Cassetes JSON não são carregados | CopyToOutputDirectory não está configurado. | Adicione o ItemGroup mostrado acima no .csproj. |
| Testes flaky com porta ocupada | Porta hardcoded usada por outro processo. | Use Port = 0 (dinâmica). Leia Server.Url depois do start. |
| Headers de auth não casam | Mapping usa WithHeader exato. | Use WithHeader("Authorization", new RegexMatcher(...)) ou ignore o header no matcher. |
| Quero regravar o cassete após mudança no provider | Cassete está estagnado, novo schema chegou. | Rode o utilitário de recording novamente e versione o novo mapping. Veja capítulo 9 para a discussão honesta sobre isso. |
Container ou cassete? · Mapa decisório
Agora que você conhece as duas ferramentas em profundidade, a decisão de qual usar deve sair quase automaticamente do mapa SOA que você desenhou no capítulo 5. Esta tabela é a referência rápida:
| Pergunta | 🐳 Testcontainers | 📼 VCR / WireMock.Net |
|---|---|---|
| Quem possui a dependência? | você Schema, deploy, versão. | outro squad ou terceiro Você só consome via HTTP. |
| Exemplos típicos | PostgreSQL, Redis, RabbitMQ, MinIO — do seu serviço. | API de pagamentos, catálogo de outro squad, Auth0, SendGrid. |
| O que está rodando? | O processo real em container Docker. | Um stub HTTP que devolve respostas pré-gravadas. |
| Custo de execução | 3–10s de startup por container. | Milissegundos. Quase grátis. |
| Pré-requisito de infra | Docker rodando local + CI. | Só .NET SDK. Roda em-process. |
| Pega bug de dialeto? | Sim JSONB, full-text, transações reais. | Não Você gravou o que o provider devolveu, nada mais. |
| Pega quebra do provider? | N/A · você é o provider. | Não Se o provider mudar, seu cassete continua verde — falsamente. Veja capítulo 9. |
| Cobre cenários de falha? | Sim — derrube o container e veja o comportamento. | Sim — crie mapping de 503/timeout/payload inválido. |
| Manutenção | Atualizar a tag da imagem Docker quando o time atualiza prod. | Regravar o cassete quando o provider mudar o contrato. |
Em SOA, mapeie as dependências do seu serviço em 2 colunas — possuídas e consumidas — antes de escrever o primeiro teste de integração. A ferramenta sai do mapa, não da preferência pessoal. Se você está hesitando entre as duas, é sinal de que o mapa ainda não está claro — volte ao capítulo 5.
⚠️ VCR NÃO é Contract Testing
Este capítulo é um aviso pedagógico. Sem ele, você sai da aula com a impressão errada de que VCR resolve "contrato em SOA" — e na primeira entrevista técnica vai pagar mico. Vamos ser explícitos: VCR / WireMock.Net resolve isolamento, NÃO resolve contrato. Os dois problemas são diferentes, exigem ferramentas diferentes, e confundi-los é fonte de bugs caros em produção.
9.1 · O que VCR faz bem
- Pipeline determinístico — sem flakiness de rede, sem depender de uptime do provider.
- CI offline — o runner nunca chama a API real depois da gravação. Roda mesmo em rede privada.
- Documentação executável — o cassete é "o payload que eu esperava do provider naquele dia".
- Velocidade — milissegundos por resposta (vs. centenas de ms na API real).
- Cenários edge controlados — você pode criar 10 mappings de erro (503, 429, payload malformado, timeout) sem precisar simular cada um no provider real.
9.2 · Os 4 pecados de usar VCR como "contract testing"
Se você (ou o tech lead) começar a chamar o cassete WireMock de "teste de contrato", surge um conjunto de problemas estruturais. Eles não são teóricos — são reais e quebram em produção:
Cassetes envelhecem em silêncio
O provider muda o schema da response (adiciona campo, renomeia, muda tipo de número para string). Você não rerecorda. Seu teste continua verde testando o passado. Em produção, na próxima vez que sua app chamar o provider, o deserializer explode. Falsa sensação de segurança.
✅ Solução: contract testing real (Pact) com verificação ativa no provider.O provider nunca é verificado
Em contract testing de verdade, o build do provider quebra quando ele viola o contrato esperado pelo consumer. Com VCR, o squad provider nem sabe que cassete existe. Ele pode quebrar a interface a qualquer momento e você só descobre quando seu serviço cair.
✅ Solução: Pact tem broker compartilhado e CI bidirecional.Sem broker, sem visibilidade
Cassetes vivem em Mappings/*.json dentro do repo do consumer. Ninguém do squad do provider olha esses arquivos. Não há painel mostrando "esses 5 consumers dependem da versão X do meu contrato". A descoberta de impacto é zero.
Matchers frágeis e ad-hoc
Mudou header de auth, timestamp no body, ordem de query string? Cassete quebra ou aceita silenciosamente, dependendo de como você configurou o matcher. Configurar matchers vira manutenção contínua, e a fronteira entre "é o mesmo contrato" e "mudou" fica subjetiva.
✅ Solução: schema-based matching (Pact, JSON Schema, OpenAPI).9.3 · O que faz contract testing de verdade
Existe uma família de ferramentas que realmente resolve o problema do contrato em SOA. A mais conhecida e canônica é Pact:
Pact em 60 segundos
- Consumer-side: o squad consumidor define no teste o contrato que espera do provider (request específica + response esperada). Pact gera um arquivo de contrato (JSON).
- Broker: o contrato é publicado num Pact Broker (serviço central). Múltiplos consumers podem publicar contratos contra o mesmo provider.
- Provider-side: o build do provider verifica que ele cumpre todos os contratos publicados pelos seus consumers. Se um consumer espera
/items/42com um schema X e o provider não responde isso, o build do provider quebra. - Versionamento: contratos têm versões. Provider pode estar em v2, mas continuar atendendo o contrato v1 enquanto consumers migram.
O resultado é verificação bidirecional: tanto consumer quanto provider falham cedo quando o contrato é violado. Esse é o problema que VCR não resolve.
Outras ferramentas válidas
- Spring Cloud Contract — equivalente do Pact no ecossistema Java/Spring.
- OpenAPI + Schemathesis — quando o spec OpenAPI é fonte da verdade, Schemathesis gera testes que verificam se o provider respeita o spec declarado.
- Postman + Newman — para times menos maduros, postman collections rodando no CI podem fazer um contract testing leve.
9.4 · Como falar disso em entrevista (sem se enrolar)
"Usei WireMock.Net para isolar dependências externas em testes de integração — pipeline determinístico, sem flakiness. Mas WireMock não é contract testing. Quando o problema é garantir contrato entre serviços em SOA, a stack canônica é Pact, que faz verificação bidirecional com broker compartilhado."
Diga isso com tranquilidade. Vai ficar claro pro entrevistador que você conhece os limites das ferramentas — que é exatamente o que separa engenheiro júnior de engenheiro pleno.
Pact não é exigência da aula 8. A aula 9 (boas práticas SOA na nuvem) abre o tema e mostra como evoluir. Esta aula te dá as ferramentas de isolamento. A próxima sobe um nível para contrato. A sequência é deliberada.
Padrões e anti-padrões em testes de integração
Conhecer as ferramentas é metade do caminho. A outra metade é evitar armadilhas comuns e seguir padrões que outros times já validaram. Esta seção lista os principais.
10.1 · Anti-padrões a evitar
Teste de integração que depende de banco compartilhado
Um banco que vários desenvolvedores e o CI compartilham. Inevitavelmente: testes interferem uns nos outros, dados sujos, debug impossível.
✅ Use Testcontainers — cada execução tem seu container descartável.Teste que chama API externa real em todo CI
Pipeline depende de uptime do provider, custos de billing, rate limits. Falhas intermitentes que não são bugs do código. Vermelho que ninguém investiga porque "é a rede de novo".
✅ Use WireMock.Net — grave uma vez, replay deterministicamente.Testar tudo via E2E
Suite gigante, lenta (1h+), frágil, cara de manter. Bug em integração precisa rodar tudo para descobrir. Time desativa testes que "estão sempre vermelhos".
✅ Pirâmide saudável: 70/25/5 unit/integração/E2E. Use E2E só para fluxos críticos de usuário.Inspeção de estado interno (white-box em integração)
Teste abre o DbContext direto para checar se o registro existe, em vez de pedir via endpoint GET. Acopla teste à implementação — qualquer refactor quebra.
Cassete VCR estagnado
Cassete gravado uma vez há 6 meses, provider mudou 3 vezes desde então. Teste continua verde, mas em produção o serviço quebra. Falso senso de segurança.
✅ Estabeleça política: regravar cassetes a cada release do provider, ou usar Pact pra ter verificação bidirecional.Dados não-determinísticos no teste
Teste usa DateTime.Now, Guid.NewGuid(), Random sem seed. Roda em horários diferentes → resultado diferente. Vira flaky.
IClock, use Bogus com seed fixo, use UUIDs determinísticos no teste.
Teste sem mensagem de erro útil
Assert.True(result) sem dizer o que era esperado. Falha sem contexto → debug demora.
Assert.Equal(esperado, atual). Em xUnit, considere FluentAssertions.
10.2 · Padrões recomendados
AAA · Arrange, Act, Assert
Todo teste tem 3 fases. Separe explicitamente, com comentários ou linhas em branco. Facilita leitura, debug e manutenção:
[Fact] public async Task NomeDescritivoDoComportamento() { // Arrange — preparar dados, mocks, estado var input = ...; // Act — executar a operação testada var result = await svc.FazerAlgo(input); // Assert — verificar o contrato observável Assert.Equal(esperado, result); }
Nome descritivo do teste
O nome do teste é a documentação. Convenções comuns:
DeveFazerXQuandoY— em português, direto.Should_DoX_When_Y— equivalente em inglês.UnitOfWork_StateUnderTest_ExpectedBehavior— formato Roy Osherove.
Um teste, uma asserção lógica
Vários Assert no mesmo teste estão ok se todos validam o mesmo comportamento. Múltiplos comportamentos? Múltiplos testes:
// ❌ Ruim — dois comportamentos no mesmo teste [Fact] public async Task TesteDoFluxoDePedido() { var pedido = await svc.CriarAsync(...); Assert.NotNull(pedido); var cancelado = await svc.CancelarAsync(pedido.Id); Assert.True(cancelado); }
// ✅ Bom — dois testes separados [Fact] public async Task DeveCriarPedidoQuandoDadosValidos() { ... } [Fact] public async Task DeveCancelarPedidoAposCriacao() { ... }
Theory para cenários parametrizados
Mesmo comportamento com inputs diferentes? Use [Theory] + [InlineData]:
[Theory] [InlineData(0, "quantidade deve ser positiva")] [InlineData(-1, "quantidade deve ser positiva")] [InlineData(10001, "quantidade máxima é 10000")] public async Task DeveRecusarQuantidadeInvalida(int qty, string msg) { var ex = await Assert.ThrowsAsync<ValidationException>( () => svc.CriarAsync(new(qty))); Assert.Contains(msg, ex.Message); }
Como entregar a ponderada
A ponderada da aula 8 é individual, em sala, 90 minutos. A entrega é via Merge Request no repositório oficial do grupo. Testcontainers e WireMock.Net (VCR) são obrigatórios na entrega — você usa as duas ferramentas no mesmo teste. Além disso, cada aluno do grupo cobre um fluxo diferente: fluxos duplicados são inválidos.
O fluxo escolhido deve obrigatoriamente tocar uma dependência possuída (BD/cache/broker do seu serviço) — testada com Testcontainers — e uma dependência consumida (API externa) — testada com WireMock.Net. Se o fluxo do MVP não tem as duas, escolha outro.
11.1 · Checklist da entrega · 10 passos
-
🤝 Coordenar no grupo · registrar fluxo único
Antes de codar, alinhe com seus colegas qual fluxo cada um vai testar. Registre a divisão numa tabela no README do repositório, com 3 colunas: aluno, fluxo escolhido, link do MR (preenchido depois). Fluxos duplicados entre alunos do mesmo grupo são considerados não-conformes — quem chega primeiro pega.
seção sugerida no README do repoMarkdown## 🧪 Tracker · Testes de Integração (Aula 8) | Aluno | Fluxo testado | MR | |--------------------|--------------------------|--------------------| | Ana Silva | POST /pedidos | !127 | | Bruno Costa | GET /catalogo/{id} | !128 | | Carla Dias | POST /pagamentos/charge | (em desenvolvim.) | | Diego Souza | PUT /pedidos/{id}/cancel | (em desenvolvim.) | -
Identificar as duas dependências do fluxo
Olhe o fluxo escolhido e mapeie: qual é a dependência possuída (geralmente o BD do serviço, mas pode ser cache ou broker)? E qual é a dependência consumida (geralmente uma API externa do parceiro ou de outro squad)? Anote as duas — vão pra descrição do MR.
-
Identificar o RF correspondente
Vá no artefato de requisitos do grupo e ache o RF (regra de negócio) que esse fluxo valida. Anote o código exato (ex.: "RF05") — vale ⭐ Excelência na rastreabilidade.
-
Identificar o eixo RNF (ISO 25010)
Qual eixo da ISO/IEC 25010 esse teste atende ou impacta? Eficiência de desempenho? Segurança? Confiabilidade? Manutenibilidade? Anote o código RNF do artefato (ex.: "RNF02").
-
Criar a branch
Padrão:
test/integration-{nome-do-fluxo}. Ex.:test/integration-criar-pedido. -
🐳 Configurar Testcontainers (dependência possuída)
Crie a fixture do container conforme o capítulo 6.
PostgreSqlBuilder(ou módulo equivalente do seu BD) +IAsyncLifetime. Migrations rodando no setup. Verifique que o container sobe local. -
📼 Configurar WireMock.Net (dependência consumida)
Crie a fixture do stub HTTP conforme o capítulo 7.
WireMockServer.Startcom porta dinâmica + mappings das responses esperadas da API externa. Aponte oHttpClientda sua app para a URL do WireMock no teste. -
Implementar 1 teste de sucesso + 1 teste de erro
Sucesso: fluxo feliz com BD real (TC) + API externa respondendo 200 (WireMock). Erro: pode ser tanto regra de negócio violada (BD recusa) quanto falha do provider (WireMock devolve 503/timeout). Padrão AAA. Black-box: só interage pelo contrato observável.
-
Garantir
dotnet testverde no pipelineO CI do repositório precisa estar verde. Se falhou, o critério 2 do barema vai cair. Push para a branch e veja o resultado antes de abrir o MR.
-
Abrir o MR com descrição completa + postar link na plataforma
A descrição do MR é parte da nota. Use o modelo abaixo. Depois de abrir o MR, atualize a tabela do README com o número do MR e cole o link direto na plataforma da disciplina antes do fim dos 90 min.
modelo da descrição do MRMarkdown## Fluxo testado Criação de pedido via POST /api/pedidos. ## Coordenação no grupo Registrado na tabela do README como meu fluxo (linha 1). Sem duplicação com colegas do grupo. ## Classificação das dependências ### Dependência possuída · Testcontainers **PostgreSQL do serviço de pedidos** — o grupo controla schema, migrations e deploy. Testado com `PostgreSqlBuilder` rodando postgres:16-alpine em container descartável. ### Dependência consumida · WireMock.Net (VCR) **API de catálogo do parceiro** — serviço externo que valida estoque antes de criar o pedido. Testado com stub HTTP que devolve 200 (sucesso) e 503 (cenário de erro). ## Rastreabilidade - **RF05** — Pedido deve ter ao menos 1 item válido e estoque positivo no catálogo (artefato de requisitos, seção 3.2). - **RNF02** — Eixo: Confiabilidade · o teste previne corrupção de dados em fluxo crítico (criação de pedido) e garante comportamento correto quando o catálogo externo cai. Sem este teste, uma alteração no schema do pedido ou queda do catálogo pode passar invisível e causar perda de pedidos. ## Cenários implementados - ✅ DeveCriarPedidoQuandoCatalogoConfirmaEstoque (sucesso) - ⚠️ DeveRecusarPedidoQuandoCatalogoIndisponivel (erro · 503) ## Pipeline - [x] `dotnet test` verde no CI - [x] Migrations rodam no setup da fixture do Postgres - [x] WireMock.Net devolve stubs determinísticos - [x] Container Postgres + WireMock descartáveis a cada execução
11.2 · Barema · 10 pontos · 5 critérios
| Critério | Peso | O que pontua máximo |
|---|---|---|
| 1 · Coordenação no grupo · Fluxo único | 1,0 pt | Fluxo registrado na tabela do README antes de codar; sem duplicação com outro aluno do grupo. 0,5 pt: fluxo único mas registro feito após a entrega. 0 pt: fluxo duplicado ou sem tabela. |
| 2 · Testes com Testcontainers + WireMock.Net | 3,0 pts | 1 sucesso + 1 erro com Testcontainers (possuída) e WireMock.Net (consumida), asserções corretas, dotnet test verde. 1,5 pt: só uma das ferramentas ou faltou cenário. 0 pt: sem testes funcionais. |
| 3 · Entrega via Merge Request | 1,5 pt | MR no repositório oficial do grupo (Open ou Merged), branch test/integration-{flow}, gitflow correto. 0,75 pt: fora do padrão. 0 pt: sem MR. |
| 4 · Rastreabilidade da Regra de Negócio (RF) | 2,0 pts | Descrição do MR cita qual RF do artefato o teste valida. ⭐ Excelência: RF exato (ex.: "RF05") + como previne falha crítica. 1,0 pt: menção vaga. 0 pt: sem RF. |
| 5 · Conexão com RNF · ISO 25010 | 2,5 pts | Eixo identificado e justificado (eficiência, segurança, confiabilidade, manutenibilidade). ⭐ Excelência: código RNF exato (ex.: "RNF02") + ligação direta com o teste. 1,0 pt: cita sem justificar. 0 pt: sem RNF. |
Aluno que sair às 16h15 sem fluxo único registrado + MR aberto + Testcontainers + WireMock + RF + RNF + pipeline verde está em débito técnico para a aula 9. Submissão parcial > submissão zerada — entregue o que conseguir.
- 0–10 min — coordenar no grupo, registrar fluxo no README (1,0 pt fácil).
- 10–25 min — setup das duas fixtures (Testcontainers + WireMock) copiando dos capítulos 6 e 7.
- 25–60 min — escrever os dois cenários (sucesso + erro) com AAA explícito.
- 60–75 min — abrir MR, copiar template da descrição, preencher RF + RNF + classificação das dependências.
- 75–90 min — refinar para os ⭐ Excelência (RF e RNF exatos do artefato), postar link na plataforma.
Bibliografia anotada
Cada referência abaixo está anotada com o capítulo onde foi usada e por que vale a leitura. Use isso para aprofundar onde fez mais sentido.
-
Martin Fowler — Testing Strategies in a Microservice Architecture
O artigo de referência sobre a pirâmide de testes adaptada para microsserviços. Lê-se em 30 minutos e dá uma base sólida para todos os capítulos 2–4 deste material. Especialmente útil a discussão sobre contract tests, que liga ao capítulo 9.
-
Testcontainers for .NET — Documentação oficial
Documentação canônica da biblioteca usada no capítulo 6. Veja especialmente a seção Modules para os builders específicos (PostgreSql, MsSql, Redis, RabbitMQ, Kafka, MongoDB, MinIO). Tem exemplos completos prontos para copiar.
-
WireMock.Net — Wiki
Referência da biblioteca usada no capítulo 7. Veja em ordem: Getting Started, Request Matching, Response Templating, Admin REST API, Proxying. A seção sobre Static Mappings mostra como versionar cassetes JSON no repo.
-
Microsoft Learn — Integration tests in ASP.NET Core
Documentação oficial da Microsoft sobre
WebApplicationFactory<Program>. Útil quando você quer testar pela camada HTTP em vez de chamar a service diretamente. Combina muito bem com Testcontainers (capítulo 6). -
Pact — Documentação oficial
A referência canônica para contract testing real (capítulo 9). Veja Getting Started e Consumer-Driven Contracts. Esta é a stack que resolve o problema que VCR não resolve. Fica como aprofundamento para quem quer ir além da aula 8.
-
ISO/IEC 25010 — Quality model
Referência rápida dos 8 eixos de qualidade (functional suitability, performance efficiency, compatibility, usability, reliability, security, maintainability, portability). Use para justificar o RNF na ponderada (capítulo 11).
-
Jay Fields — Working Effectively with Unit Tests
Livro pago, mas vale o investimento se você quer levar testes a sério na carreira. Trata especialmente bem dos padrões AAA, nomenclatura de testes, e a distinção entre solitary tests (unit puro) e sociable tests (integração).
-
Vladimir Khorikov — Unit Testing Principles, Practices, and Patterns
Outro livro fundamental. Especial atenção ao capítulo sobre Integration testing best practices — alinha quase perfeitamente com o que está nesta aula. Útil para aprofundar a discussão sobre black-box (capítulo 4).
Antes da aula 9, releia o capítulo 9 deste material e o Getting Started da Pact. A aula 9 (SOA na nuvem) vai conectar os testes de integração de hoje com smoke tests pós-deploy e com contract testing de verdade. Você vai chegar pronto para evoluir o que aprendeu aqui.