ConstructiCat Logo
CodeBust.
Browse section ▾

Lógica de Teste em Produção.

O código de produção contém lógica, ramos ou membros que existem apenas para dar suporte aos testes, borrando a linha entre o que vai para produção e o que é apenas testado.

##Signs and Symptoms

Você encontra código no sistema sob teste (SUT) que só importa quando um teste está rodando. Sinais reveladores:

  • Hooks de teste / flags de modo — ramos acionados por uma flag testing, isTest, NODE_ENV === 'test' ou mock que curto-circuitam o comportamento real.
  • Membros "apenas para testes" — setters, getters, reset() ou construtores públicos adicionados unicamente para que um teste possa alcançar o estado interno, muitas vezes marcados com @VisibleForTesting / @TestOnly e então de fato chamados pela produção.
  • Poluição de igualdade — lógica de equals() / comparação adicionada a uma classe de produção apenas para que uma asserção possa comparar dois objetos.
  • Dependência de teste em produção — módulos de produção importando um framework de teste, fixture ou fábrica de mocks.
// SMELL: o código entregue se comporta de forma diferente quando "testing" está ligado
class PaymentService {
  charge(order: Order) {
    if (process.env.NODE_ENV === 'test' || this.isTesting) {
      return { status: 'ok', id: 'FAKE-TEST-ID' }; // gateway real nunca é exercitado
    }
    return this.gateway.charge(order); // <-- o caminho que realmente vai para produção
  }
}

O ramo "real" é aquele que seus clientes atingem, e é exatamente o ramo que seus testes pulam.

##Reasons for the Problem

Por que acontece

  • O SUT é difícil de testar (ele se comunica com uma rede, relógio, gateway de pagamento ou sistema de arquivos) e adicionar um atalho if (testing) é mais rápido do que introduzir uma costura (seam) adequada.
  • Um teste precisa observar ou definir o estado interno, então um desenvolvedor o expõe "só para o teste."
  • Criar stubs/mocks é trabalhoso, então dados predefinidos são embutidos no código atrás de uma flag.

Por que prejudica

  • Falsa confiança. Os testes exercitam o ramo exclusivo de teste, então o caminho de produção é entregue sem ser testado. Testes verdes provam que o fake funciona, não a coisa real.
  • Confiabilidade / segurança. Código exclusivo de teste que sobrevive até a produção pode rodar em condições reais. A história canônica de advertência de Meszaros é a do Ariane 5: código destinado apenas ao solo, deixado ativo em voo, desencadeou a falha. Um if (isTesting) deixado habilitado é a versão em software disso.
  • Segurança. Desvios de teste são backdoors — uma flag que pula autenticação, pagamento ou validação está a um erro de configuração de distância de se tornar explorável.
  • Legibilidade e inchaço de API. Setters/getters/reset() exclusivos de teste ampliam a superfície pública e enganam os clientes reais sobre para que serve a classe.
  • Manutenibilidade. Dois comportamentos vivem em uma única classe; toda alteração precisa considerar tanto o caminho de produção quanto o de teste, e a divergência apodrece silenciosamente.

##Treatment

Tire a lógica de teste do código de produção introduzindo uma costura (seam) adequada em vez de uma flag.

  1. Injete a variação (Injeção de Dependência + Test Double). Substitua o ramo embutido por um colaborador que o teste substitui. A produção conecta a implementação real; o teste conecta um fake/stub/mock.
  2. Use uma Subclasse Específica para Teste quando você só precisa sobrescrever um método — sobrescreva-o em uma subclasse que vive no código de teste, e não por meio de um if na classe base.
  3. Aplique o padrão Humble Object para puxar a lógica difícil de testar (relógio, E/S) para trás de um adaptador fino, de modo que a lógica central se torne diretamente testável sem hooks.
  4. Mova a lógica de comparação para o lado do teste. Em vez de poluir a produção com equals() para asserções, use um matcher/comparador personalizado ou faça asserções sobre os campos que importam para você.
  5. Mantenha o código de teste fora do build. Use separação de source-set / configuração de build para que os helpers de teste não possam ser compilados no artefato entregue; marque os membros genuinamente visíveis para teste com @VisibleForTesting/@TestOnly e deixe um linter garantir que a produção nunca os chame.
// ANTES: hook de teste dentro do código de produção
class PaymentService {
  charge(order: Order) {
    if (this.isTesting) return { status: 'ok', id: 'FAKE-TEST-ID' };
    return this.gateway.charge(order);
  }
}

// DEPOIS: um único caminho de código; o gateway é injetado e simulado no teste
class PaymentService {
  constructor(private gateway: PaymentGateway) {}
  charge(order: Order) {
    return this.gateway.charge(order); // mesmo caminho em produção e em teste
  }
}

// teste
const fakeGateway = { charge: () => ({ status: 'ok', id: 'FAKE-TEST-ID' }) };
const service = new PaymentService(fakeGateway);

Agora o caminho de produção é o único caminho, e o teste controla o comportamento de fora.

##Detected by