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

Apenas Para Testadores.

Classes de produção carregam membros que existem apenas para os testes — getters de estado, métodos reset(), membros com visibilidade ampliada e marcados com @VisibleForTesting —, poluindo a API real e convidando a testes que verificam detalhes internos em vez do comportamento.

##Sinais e sintomas

Você encontra membros no código de produção cujos únicos chamadores vivem em arquivos de teste. Sinais reveladores:

  • Métodos, getters/setters, exports ou parâmetros de construtor usados exclusivamente a partir de *.test.ts / *.spec.ts.
  • Nomes ou marcadores com cara de teste: getStateForTest, resetForTesting, __getInternal, FTO_*, forTest, comentários como // only used by tests, ou anotações como @VisibleForTesting / @TestOnly / @internal.
  • Visibilidade relaxada (um campo tornado public/exportado, um #private transformado em protected) puramente para que um teste consiga alcançar o estado interno.
  • Mutadores só para testes — reset(), setClockForTest(...), setRandomForTest(...) — adicionados apenas porque um teste precisou rebobinar ou conduzir o objeto, não porque algum chamador de produção precise. (Ramificações baseadas em uma flag de "modo de teste", dependências exclusivas de teste e métodos de igualdade adicionados só para as asserções pertencem à Lógica de Teste em Produção.)
// payment-service.ts  (código de PRODUÇÃO)
export class PaymentService {
  #ledger: Entry[] = [];
  private now: () => Date = () => new Date();

  charge(amount: number) { /* ... */ }

  // Nada em produção jamais chama estes — apenas os testes chamam:
  getLedgerForTest() { return this.#ledger; }          // expõe detalhes internos
  setClockForTest(now: () => Date) { this.now = now; } // seam exclusivo para testes
  FTO_reset() { this.#ledger = []; }                   // "For Tests Only"
}

Uma verificação rápida: grep -rn 'forTest\|ForTesting\|FTO_' src/ que retorne ocorrências no código-fonte de produção, ou uma varredura de código morto (por exemplo, Knip em modo de produção) que reporte um export como não utilizado enquanto um teste claramente o importa.

##Causas do problema

Por que acontece

  • Adaptar testes a código não testável. Quando o código legado não foi projetado para ser testável, a forma mais rápida de fazer asserções sobre um resultado é abrir um buraco no SUT e ler suas entranhas. O nome do smell (For Testers Only) vem de van Deursen et al. (2001), que descreveram classes de produção com métodos usados apenas pelos testes; mais tarde, Meszaros registrou o mesmo problema como For Tests Only, uma das causas da Lógica de Teste em Produção.
  • APIs assimétricas. Os clientes reais usam um objeto de uma forma (escrita); os testes o usam de forma simétrica (escrever e então ler de volta para verificar), de modo que os testadores "precisam" de acessadores que nenhum chamador de produção precisa.
  • Pressão de prazo. Adicionar uma porta dos fundos é mais barato no momento do que refatorar para um design no qual o comportamento é observável por meio do contrato público.

Por que é prejudicial

  • Legibilidade. A API pública deixa de dizer a verdade — os mantenedores não conseguem distinguir o contrato real do andaime de teste, e cada leitor tem que se perguntar "esse método é realmente usado?"
  • Encapsulamento e confiabilidade. O estado interno se torna alcançável e mutável no código que vai para produção. Uma chamada perdida a FTO_reset() ou um setter vazado pode corromper o estado em produção; a superfície extra também incha o bundle e amplia a superfície de ataque.
  • Falsa confiança. Testes que cutucam o estado privado fazem asserções sobre a implementação, não sobre o comportamento. Eles podem continuar verdes enquanto o contrato público está quebrado, e quebram em refatorações inofensivas — testes frágeis que testam a coisa errada.
  • Manutenibilidade. Os membros exclusivos para testes parecem código morto, mas não podem ser excluídos; cada mudança precisa levar em conta chamadores fantasmas, e o smell tende a se multiplicar à medida que mais testes reutilizam a porta dos fundos.

##Tratamento

Trate a porta dos fundos como um sinal de design, não como um detalhe de fixture.

  1. Teste primeiro por meio do comportamento observável. Faça asserções sobre valores de retorno, eventos emitidos, saída persistida ou interações com colaboradores (via test doubles) em vez de alcançar os detalhes internos. A maioria dos acessadores getXForTest desaparece assim que você verifica o que o objeto faz, e não o que ele guarda.
  2. Use uma Test-Specific Subclass quando você genuinamente precisar de acesso interno. Estenda a classe no teste para expor um membro protected, em vez de ampliar a visibilidade em produção.
  3. Faça das seams parte do design real, não escotilhas exclusivas para testes. Injetar um relógio ou um RNG por meio do construtor normal é injeção de dependência legítima; um mutador setClockForTest() é um smell. Se uma seam só faz sentido para os testes, empurre o comportamento para um Strategy/Null Object que a produção instala por padrão e que o teste troca.
  4. Se a exposição for realmente inevitável, rotule-a de forma evidente e isole-a. Marque-a com @VisibleForTesting / @internal (ou uma convenção de nomenclatura FTO_) e garanta que o código de produção nunca a chame (veja os detectores). Uma seam marcada e protegida é melhor do que uma silenciosa.
  5. Cace os infratores existentes. Faça grep por forTest/ForTesting/FTO_ e rode uma ferramenta de uso/código morto, como o Knip em modo de produção, para revelar exports referenciados apenas por arquivos de teste.
// ANTES — a produção carrega um acessador exclusivo para testes
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  getItemsForTest() { return this.#items; } // apenas para testadores
}
// teste
expect(cart.getItemsForTest()).toHaveLength(1);
// DEPOIS — verifique o comportamento por meio do contrato real
export class Cart {
  #items: Item[] = [];
  add(i: Item) { this.#items.push(i); }
  get count() { return this.#items.length; }
  get total() { return this.#items.reduce((s, i) => s + i.price, 0); }
}
// teste
cart.add({ price: 10 });
expect(cart.count).toBe(1);
expect(cart.total).toBe(10);

Se você ainda precisar de acesso interno, restrinja a seam aos testes com uma subclasse, em vez de ao mundo inteiro:

// a produção permanece limpa: queue é protected, não public
export class Scheduler {
  protected queue: Job[] = [];
  enqueue(j: Job) { this.queue.push(j); }
}
// apenas no arquivo de teste
class TestScheduler extends Scheduler {
  peek() { return this.queue; }
}

##Detectado por

  • sonar java:S5803 — Membros "@VisibleForTesting" não devem ser acessados a partir do código de produção