ConstructiCat Logo
CodeBust.
Browse section ▾

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.

##Signs and Symptoms

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.
  • Dados de Teste Hard-Coded — literais mágicos (42, "a3f9-...", userId=7) sem significado nomeado, repetidos na configuração e nas asserções.
  • 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.

##Reasons for the Problem

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.

##Treatment

Faça com que todo teste conte uma história autocontida cuja intenção fique visível no corpo do teste.

  1. Verifique uma condição por teste. Divida um Eager Test em testes focados, cada um nomeado pelo comportamento que verifica. Isso também corrige o mascaramento de cobertura.
  2. Coloque o fixture relevante inline (elimine o Mystery Guest). Construa os dados de que o teste depende no próprio teste, ou por meio de um método Creation/Builder explícito e nomeado — não carregue arquivos anônimos nem linhas de banco compartilhadas.
  3. Use um Fixture Mínimo / Fresco. Construa apenas o que este teste precisa; substitua um beforeEach amplo e compartilhado por um builder que define valores padrão para o ruído e deixa cada teste definir apenas o campo sob teste.
  4. Nomeie seus dados. Substitua literais mágicos por constantes/variáveis que revelem a intenção, de modo que a asserção se explique por si só.
  5. Afirme sobre o significado, não sobre tudo. Prefira asserções direcionadas a um snapshot gigante; se usar snapshot, mantenha-o pequeno e revisável para que os fatos relevantes não se afoguem em saída irrelevante.
  6. Esconda a mecânica, não a intenção. Empurre a fiação incidental para métodos Test Utility/auxiliares bem nomeados, de modo que o corpo do teste se leia como configuração → ação → expectativa.
// 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: 'ada@example.com' }); // o builder define valores padrão para o ruído

  const created = await svc.create(newUser);

  expect(created.email).toBe('ada@example.com');
  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.

##Detected by