QA · Leitura de 8 min

Testes de integração: quando as peças passam sozinhas mas falham juntas

Cada módulo passa nos testes unitários e mesmo assim o sistema falha. O bug mora na costura entre componentes, e só a integração o pega ali.

Um teste de integração verifica que dois ou mais componentes que funcionam bem separadamente também funcionam bem quando são conectados. Ele não examina uma função isolada, como o teste unitário, mas a costura entre as peças: a chamada de um módulo a outro, a consulta que vai ao banco de dados, a resposta que chega de um serviço externo. O erro que ele procura não mora dentro de um componente, e sim no espaço entre eles.

Essa é a resposta curta. A resposta útil é entender por que existe esse nível intermediário de teste, por que não basta ter cada módulo bem testado por conta própria, e quando vale a pena pagar seu custo. Porque a integração custa mais que o teste unitário: é mais lenta, mais frágil e mais difícil de diagnosticar. A pergunta não é se usá la, e sim onde.

01 · a costuraO que a integração testa e o teste unitário não consegue ver

Lembre da pirâmide de testes. Na base ficam muitos testes unitários, rápidos e isolados. No meio, menos testes de integração. No topo, poucos de ponta a ponta. A integração ocupa o segundo nível porque seu alcance é intermediário: mais que uma unidade, menos que o sistema completo.

O problema que ela resolve é concreto. Um teste unitário examina uma função sem dependências reais: para isso, substitui tudo ao seu redor por substitutos controlados. É exatamente o que o torna rápido e confiável, mas também o que o deixa cego para um tipo de falha. Se o módulo A supõe que o módulo B devolve uma data em um formato, e B na verdade a devolve em outro, os testes unitários de A e de B passam os dois. Cada um testou seu lado do contrato com um substituto que respeitava a suposição errada. O sistema falha apenas quando A e B conversam de verdade.

Um bug de integração não está dentro de nenhum componente. Está na suposição que um componente faz sobre outro.

Isso se chama contrato: o acordo implícito ou explícito sobre o que um componente envia e o que espera receber. Formato dos dados, ordem dos campos, o que acontece quando algo dá errado, quanto demora uma resposta. O teste de integração é, no fundo, uma verificação de que ambos os lados entendem o contrato da mesma maneira. A ISTQB define o teste de integração precisamente como aquele que se concentra nas interações entre componentes ou sistemas [1].

Figura 1 · o bug mora na costura
Módulo A unitários: OK Módulo B unitários: OK o contrato falha aqui Cada módulo passa em seus testes em isolamento. A conexão, não.
A espera uma data em um formato; B a envia em outro. Seus testes unitários passam porque cada um testou seu lado com um substituto que respeitava a suposição errada. Só a integração toca a costura onde mora o erro.

02 · a decisãoDublês de teste ou dependências reais

Aqui aparece a decisão que define um teste de integração: você usa o componente real do outro lado da costura, ou um substituto controlado? Esses substitutos se chamam dublês de teste, por analogia com os dublês de risco do cinema. Há vários tipos, e convém não misturar os nomes: um stub devolve respostas fixas preparadas de antemão; um mock além disso verifica que foi chamado como você esperava; um fake é uma implementação leve mas funcional, como um banco de dados em memória em vez do real. Gerard Meszaros organizou e nomeou esses padrões em seu catálogo de xUnit [2].

A regra prática é simples de enunciar e difícil de aplicar: use dependências reais quando o que você quer testar é justamente a integração com essa dependência, e use dublês quando essa dependência é apenas ruído para o teste que importa.

Se você está testando que seu código grava e recupera bem os dados de um banco, use um banco de dados real, ou ao menos um fake fiel, porque o ponto do teste é essa conversa. Substituir o banco por um stub que sempre diz "gravado" não provaria nada: você estaria testando o stub. Por outro lado, se você está testando a lógica de um módulo que, de passagem, envia um e mail, não suba um servidor de e mail de verdade: coloque um dublê que registre que uma tentativa de envio foi feita. O e mail não é o que você está verificando.

Quanto mais dublês você coloca, mais rápido e estável fica o teste, e menos ele se parece com a realidade. Esse é o trade off, sempre.

O risco de abusar dos dublês

Um dublê codifica uma suposição sobre como o componente real se comporta. Se essa suposição estiver errada, ou ficar desatualizada quando o componente muda, seu teste continua verde enquanto o sistema real já está quebrado. Um teste que passa sobre uma premissa falsa é pior do que não ter teste: dá confiança sem respaldo. Por isso as dependências reais, embora mais caras, seguem insubstituíveis justamente nas costuras que mais importam para você.

03 · o custoPor que detectar tarde uma falha de integração sai caro

Tudo isso tem uma razão econômica no fundo. O custo de corrigir um defeito cresce à medida que ele avança pelas etapas do desenvolvimento. Uma falha que você pega ao escrever o código custa pouco: você a corrige em minutos, com todo o contexto fresco na cabeça. A mesma falha descoberta em produção, depois de passar por integração, testes de sistema e implantação, custa muito mais: é preciso reproduzi la, rastrear quais componentes intervêm, coordenar a correção e implantar de novo. Essa ideia, de que o custo da correção escala com a distância entre a introdução do defeito e sua detecção, é um princípio clássico da engenharia de software [3].

As falhas de integração são especialmente caras de detectar tarde por uma razão: são difíceis de atribuir. Quando o sistema completo falha, você não sabe se a culpa é de A, de B ou de como eles conversam. O sintoma aparece longe da causa. Um bom conjunto de testes de integração encurta essa distância: em vez de descobrir o problema quando um usuário relata algo estranho, você o descobre quando o teste que exercita aquela costura específica fica vermelho. Você sabe quais duas peças deixaram de se entender.

Figura 2 · o custo cresce com a demora
Código Integração Sistema Produção custo de corrigir
Quanto mais longe de sua origem um defeito de integração é detectado, mais custa atribuí lo e corrigi lo. Os testes de integração empurram a detecção para a esquerda, onde corrigir ainda é barato.

04 · o equilíbrioQuando usá los e quantos convém ter

A forma da pirâmide já traz a resposta: convém ter menos testes de integração que unitários e mais que de ponta a ponta. Não porque valham menos, mas porque cada um custa mais para escrever e manter, e demora mais para rodar. Se você tenta cobrir tudo com integração, acaba com uma suíte lenta e frágil que o time deixa de executar. Se você não tem nenhum, cada implantação é uma aposta sobre costuras que ninguém verificou.

A orientação prática é priorizar por risco. Escreva testes de integração nas costuras onde uma falha seria cara ou difícil de detectar: a fronteira com o banco de dados, a conversa com um serviço de pagamento, o ponto onde seu código recebe dados de um terceiro que você não controla. Deixe de fora o que um teste unitário já cobre bem: a lógica interna de um módulo não precisa de um banco de dados real para ser verificada.

O teste de integração não substitui nem o unitário nem o de ponta a ponta. Ele se situa entre os dois para cobrir o ponto cego de ambos: o lugar onde peças corretas, cada uma por conta própria, deixam de se entender quando finalmente se conectam. Esse ponto cego é real, é frequente, e é caro se você o descobre tarde. Por isso este nível da pirâmide existe.

Fontes

  1. International Software Testing Qualifications Board (ISTQB). Certified Tester Foundation Level (CTFL) Syllabus, v4.0 (2023). Seção sobre níveis de teste: definição de integration testing como foco nas interações entre componentes ou sistemas. istqb.org.
  2. Meszaros, G. (2007). xUnit Test Patterns: Refactoring Test Code. Addison-Wesley. Catálogo de dublês de teste (Test Double): stub, mock, fake, spy e dummy.
  3. International Organization for Standardization. ISO/IEC/IEEE 12207:2017, Systems and software engineering: Software life cycle processes. Marco de referência sobre processos de verificação e detecção precoce de defeitos ao longo do ciclo de vida.

Aprenda mais sobre IA

Ver tudo Aprenda IA