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'oumockque 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/@TestOnlye 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.
- 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.
- 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
ifna classe base. - 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.
- 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ê. - 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/@TestOnlye 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
- codeql java/visible-for-testing-abuse — Uso de VisibleForTesting em código de produção
- deepsource JAVA-A1067 — Métodos @VisibleForTesting/@TestOnly não devem ser usados em código que não seja de teste
- android-lint VisibleForTests — Visível Apenas Para Testes