ConstructiCat Logo
CodeBust.
Browse section ▾

Testando Detalhes de Implementação.

Um teste faz asserções sobre como o código funciona internamente — campos privados, chamadas a métodos internos, estrutura do DOM ou classes CSS — em vez do comportamento observável do qual um consumidor real depende.

##Signs and Symptoms

Você reconhece este sintoma quando um teste vai além do contrato público e fixa a maquinaria por trás dele. Sinais comuns:

  • Asserções sobre estado/campos internos ou privados, ou invocação direta de métodos privados (muitas vezes via casts, reflexão ou // @ts-expect-error).
  • Espionar ou afirmar que um helper interno foi chamado (expect(internalCalc).toHaveBeenCalled()) em vez de verificar o resultado.
  • Testes de UI que consultam por classe CSS, tag, hooks data-* ou posição no DOM em vez de por role/label/texto — por exemplo container.querySelector('.btn-primary > span:nth-child(2)'), wrapper.state(), wrapper.find('SomeChildComponent').props().
  • Testes de snapshot sobre árvores de renderização inteiras / objetos internos serializados, de modo que qualquer ajuste de marcação falha.
  • Testes que quebram a cada refatoração mesmo que a funcionalidade continue funcionando (falsos negativos) e, inversamente, continuam passando após um bug de lógica porque apenas reverificam a ligação interna (falsos positivos).
// SMELL: acopla o teste aos internos do componente e à estrutura do DOM
test('counter increments', () => {
  const wrapper = mount(<Counter />);
  wrapper.instance().handleClick();          // chama um método privado diretamente
  expect(wrapper.state('count')).toBe(1);    // faz asserção sobre estado interno
  expect(wrapper.find('.count-display').text()).toBe('1'); // seletor CSS frágil
});

O mesmo padrão aparece no lado servidor: expect(service._cache.size).toBe(1) ou afirmar a sequência exata de chamadas internas que um método faz.

##Reasons for the Problem

Por que acontece

  • O estado interno é fácil de alcançar — um campo público, um helper exportado ou container.querySelector está logo ali, enquanto exercitar o comportamento real exige mais preparação.
  • Perseguir métricas de cobertura: testar cada método privado 1:1 dá a sensação de minuciosidade.
  • O uso excessivo de mocks empurra as pessoas a afirmar "este colaborador foi chamado" em vez de "a coisa certa aconteceu."
  • Ferramentas que incentivam isso: shallow rendering / APIs instance() / state(), ou capturar nós pelo nome da classe.

Por que é prejudicial

  • Manutenibilidade / fragilidade. Este é o Teste Frágil de Meszaros, causado por Software Superespecificado: o teste fixa um comportamento que o consumidor nunca exigiu, de modo que refatorações inofensivas (renomear um método, reestruturar a marcação, alterar um campo privado) quebram testes que estavam verdes sem motivo real. Os testes se tornam um imposto sobre a refatoração em vez de uma rede de segurança.
  • Falsa confiança (o perigo central, segundo Kent C. Dodds). Testes de detalhes de implementação falham nas duas direções erradas: falsos negativos (o teste fica vermelho embora a funcionalidade ainda funcione) e falsos positivos (o teste permanece verde embora a funcionalidade esteja quebrada — você verificou a ligação interna, não o resultado). De qualquer forma, a suíte deixa de lhe dizer a verdade.
  • Legibilidade. O teste documenta como o código é construído, não o que ele garante. Quem lê não consegue identificar qual comportamento realmente importa, e o teste deixa de servir também como exemplo de uso ou especificação.
  • Acoplamento. Ele fixa as decisões de design atuais, desencorajando exatamente as refatorações que os testes deveriam tornar seguras.

##Treatment

Teste através do contrato público — a mesma superfície que um chamador ou usuário real toca — e faça asserções sobre a saída observável: valores de retorno, erros lançados, eventos emitidos, estado persistido ou UI renderizada/visível.

  1. Identifique o consumidor. Para um módulo, é sua API exportada; para um componente de UI, é o usuário (cliques, digitação) e o que ele consegue ver.
  2. Forneça as entradas como um consumidor faria, e não chamando métodos privados. Dispare um clique real em vez de invocar o handler; chame o método público em vez do helper.
  3. Faça asserções sobre resultados, não sobre internos. Substitua verificações de state()/campo privado/toHaveBeenCalled por verificações sobre o que sai.
  4. Consulte a UI por acessibilidade, não por estrutura — role, label, texto — em vez de classes CSS, tags ou nth-child.
  5. Pare de testar métodos privados diretamente. Cubra-os através do método público que os utiliza; se uma unidade privada for complexa o suficiente para precisar de seus próprios testes, isso é um sinal de que ela deve ser extraída para seu próprio módulo com sua própria API pública.
  6. Reserve asserções de mock/spy para fronteiras verdadeiras (rede, tempo, gateway de pagamento) onde a própria chamada é o comportamento observável — não para colaboradores internos.
// ANTES: testa detalhes de implementação
const wrapper = mount(<Counter />);
wrapper.instance().handleClick();
expect(wrapper.state('count')).toBe(1);
expect(wrapper.find('.count-display').text()).toBe('1');

// DEPOIS: testa o comportamento observável via contrato público voltado ao usuário
render(<Counter />);
await userEvent.click(screen.getByRole('button', { name: /increment/i }));
expect(screen.getByText('1')).toBeInTheDocument();

Regra prática: se uma refatoração que preserva o comportamento quebra o teste, o teste estava verificando um detalhe de implementação.

##Detected by