Logotipo do ConstructiCat
CodeBust.
Explorar a seção ▾

Teste Obscuro.

Um Teste Obscuro é aquele em que o leitor não consegue dizer, apenas pelo método de teste, qual cenário é montado, qual comportamento é exercitado e qual resultado é esperado — porque a intenção está soterrada em detalhes demais, contexto de menos ou lógica escondida em outro lugar.

##Sinais e sintomas

Você lê um teste e ainda não consegue responder a três perguntas: qual é a configuração, o que está sendo exercitado e qual é o resultado esperado? A cadeia de causa/efeito está escondida. Meszaros agrupa as causas em "informação demais" e "informação de menos". Sinais comuns:

  • Eager Test — um único método de teste verifica muitos comportamentos não relacionados, de modo que nenhuma intenção isolada fica clara.
  • Mystery Guest — o fixture ou os valores esperados vivem fora do teste (um arquivo, um registro de banco compartilhado, um fixture carregado por nome), então você não consegue ver por que a asserção deveria passar.
  • General Fixture — um grande beforeEach/factory compartilhado constrói muito mais do que este teste precisa; os poucos campos relevantes se perdem no ruído.
  • Informação Irrelevante — páginas de dados de configuração ou um snapshot gigante em que apenas um ou dois campos de fato importam. Tratado em detalhe em Teste Verboso.
  • Dados de Teste Hard-Coded — literais mágicos (42, "a3f9-...", userId=7) sem significado nomeado, repetidos na configuração e nas asserções. Veja Número Mágico em Teste.
  • Indirect Testing — o teste cutuca o sistema sob teste por meio de vários outros objetos, obscurecendo o que realmente está sob teste.
// Obscuro: Eager + Mystery Guest + Informação Irrelevante
test('user', async () => {
  const data = loadFixture('users.json');        // mystery guest: os valores vivem em um arquivo
  const svc = new UserService(data, cfg, clock); // general fixture: a maior parte disto não é usada
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });

  expect(u.id).toBeDefined();
  expect(u.email).toContain('@');
  expect(await svc.count()).toBe(data.length + 1); // por que esse número? a resposta está no arquivo
  expect(await svc.login(u.email, 'p@ss')).toBe(true); // segundo comportamento não relacionado
});

Aqui você não consegue dizer o que o teste prova sem abrir users.json, e na verdade ele verifica criação e login de uma só vez.

##Causas do problema

Por que acontece

  • Os testes crescem por acúmulo: um desenvolvedor adiciona "mais uma asserção" a um teste existente em vez de escrever um novo, produzindo um Eager Test.
  • Compartilhar a configuração parece eficiente, então um único fixture amplo ou beforeEach é reutilizado em todo lugar (General Fixture), e arquivos externos/seeds de banco são carregados por nome (Mystery Guest).
  • Copiar e colar dados de aparência realista arrasta dezenas de campos irrelevantes e números mágicos.
  • Excesso de mocking ou passar por muitos colaboradores transforma um teste de unidade em Indirect Testing.

Por que isso prejudica

  • Legibilidade: um teste é a documentação do comportamento pretendido. Se o leitor não consegue reconstruir Setup → Exercício → Verificação, esse valor de documentação se perde.
  • Manutenibilidade: quando um teste obscuro falha, você não sabe qual comportamento quebrou ou se o erro está no teste ou no código, então as mudanças são lentas e arriscadas.
  • Confiabilidade: Mystery Guest e General Fixture acoplam o teste a estado externo/compartilhado, causando falhas instáveis ou dependentes de ordem (e quebrando a propriedade "fresca e determinística" de um bom teste de unidade).
  • Falsa confiança: Eager Tests mascaram a cobertura — uma falha no início do método interrompe as verificações posteriores, então comportamentos que você pensa estarem testados podem nunca rodar. E, como observa Meszaros, erros de programação são mais fáceis de esconder em um teste obscuro, produzindo Buggy Tests que passam pelos motivos errados.

##Tratamento

Não existe um remédio único para o Teste Obscuro, porque "obscuro" é um sintoma com várias causas. Diagnostique primeiro e depois aplique o remédio da causa encontrada — cada uma tem seu próprio artigo com as refatorações detalhadas.

  1. Faça uma leitura a frio. Peça a alguém que não viu o código para dizer, só pelo corpo do teste, qual é a preparação, a ação e o resultado esperado. Onde a pessoa precisar abrir outro arquivo ou adivinhar, está a causa.
  2. Vários comportamentos não relacionados em um teste? É um Teste Ávido: divida-o em testes focados, cada um nomeado pelo comportamento que verifica. Assim, uma falha precoce também deixa de esconder as verificações seguintes.
  3. Valores esperados justificados por um arquivo externo, um seed ou um registro compartilhado? É um Convidado Misterioso: traga para o teste os dados dos quais a asserção depende.
  4. Um beforeEach compartilhado e amplo constrói muito mais do que o teste precisa? É um General Fixture: passe a usar uma fixture mínima e nova para cada teste.
  5. Páginas de preparação irrelevante ou asserções campo a campo? É um Teste Verboso: esconda o ruído atrás de Creation Methods e builders e compare com um objeto esperado — a mecânica está detalhada lá.
  6. Literais sem explicação? Veja Número Mágico em Teste: dê nomes aos valores para que a asserção se explique sozinha.
  7. O sistema sob teste só é alcançado através de vários outros objetos (Indirect Testing)? Teste-o pela própria interface ou extraia a lógica para poder testá-la diretamente.
// Antes (obscuro)
test('user', async () => {
  const data = loadFixture('users.json');
  const svc = new UserService(data, cfg, clock);
  const u = await svc.create({ ...data[0], roles: ['a','b'], flags: 0x1f });
  expect(await svc.count()).toBe(data.length + 1);
});

// Depois: uma intenção, fixture inline, dados nomeados
test('create() persists a new user', async () => {
  const svc = userServiceWith([]);                 // fixture mínimo e fresco
  const newUser = aUser({ email: '[email protected]' }); // o builder define valores padrão para o ruído

  const created = await svc.create(newUser);

  expect(created.email).toBe('[email protected]');
  expect(await svc.count()).toBe(1);               // autoexplicativo, sem arquivo externo
});

Busque o formato AAA (Arrange-Act-Assert): o leitor deve compreender o cenário sem sair do método de teste.

##Detectado por