Escrever testes para código cujo objetivo não entendo

59

Recentemente, concluí uma refatoração de caixa preta. Não consigo fazer check-in, porque não consigo descobrir como testá-lo.

Em um nível alto, eu tenho uma classe cuja inicialização envolve capturar valores de alguma classe B. Se a classe B estiver "vazia", ​​ela gera alguns padrões sensíveis. Eu extraí esta parte para um método que inicializa a classe B com os mesmos padrões.

Ainda tenho que descobrir o propósito / contexto de qualquer classe, ou como elas seriam usadas. Portanto, não consigo inicializar o objeto a partir de uma classe B vazia e verificar se ele tem os valores certos / faz a coisa certa.

Minha melhor idéia é executar o código original, codificar os resultados de métodos públicos, dependendo dos membros inicializados, e testar o novo código com relação a isso. Não sei bem por que me sinto vagamente desconfortável com essa ideia.

Existe um ataque melhor aqui?

JETM
fonte
28
Eu sinto que você começou do lado errado. Você deve primeiro entender o código, testá-lo e refatorar. Por que você está refatorando sem saber para que serve o código?
23817 Jacob Raihle
11
@JacobRaihle É um programa bastante especializado para pessoas com formação em coisas que nunca toquei. Estou pegando o contexto à medida que avanço, mas simplesmente não é prático esperar para ter um entendimento sólido antes de começar.
JETM 23/02
4
O que não é prático é reescrever as coisas e, quando as mudanças estão em produção, descobrir por que você não deveria. Se você conseguir fazer um teste completo antes disso, tudo bem, essa pode ser uma boa maneira de conhecer a base de código. Caso contrário, é imperativo que você entenda antes de mudar.
23817 Jacob Raihle
37
Há um tipo específico de teste chamado Teste de caracterização para quando você deseja testar o comportamento real do sistema. Você apenas pega seu sistema original e adiciona testes que afirmam o que ele realmente faz (e não necessariamente o que se destinava a fazer!). Eles servem como um andaime ao redor do seu sistema, que você pode modificar com segurança, pois garante que ele mantenha seu comportamento.
Vincent Savard
3
Você não pode pedir / revisar por alguém que o entende?
Pjc50 23/02

Respostas:

122

Você está indo bem!

Criar testes de regressão automatizados geralmente é a melhor coisa que você pode fazer para tornar um componente refatorável. Pode ser surpreendente, mas esses testes geralmente podem ser escritos sem a compreensão completa do que o componente faz internamente, desde que você entenda as "interfaces" de entrada e saída (no significado geral dessa palavra). Fizemos isso várias vezes no passado para aplicativos legados completos, não apenas para classes, e isso frequentemente nos ajudou a evitar quebras que não entendíamos completamente.

No entanto, você deve ter dados de teste suficientes e garantir um entendimento firme do que o software faz do ponto de vista de um usuário desse componente; caso contrário, corre o risco de omitir casos de teste importantes.

É uma boa ideia IMHO implementar seus testes automatizados antes de iniciar a refatoração, e não depois, para que você possa fazer a refatoração em pequenas etapas e verificar cada etapa. A refatoração em si deve tornar o código mais legível, ajudando a aumentar a compreensão dos internos pouco a pouco. Portanto, as etapas do pedido nesse processo são

  1. entender o código "de fora",
  2. escreva testes de regressão,
  3. refator, o que leva a uma melhor compreensão das partes internas do código
Doc Brown
fonte
21
Resposta perfeita, também exatamente como descrito no livro "Trabalhar com Código Legacy"
Altoyr
Eu tive que fazer algo assim uma vez. Colete dados de saída típicos do aplicativo antes de modificá-lo e verifique minha nova versão do aplicativo executando os mesmos dados de teste nele. 30 anos atrás ... Fortran ... Era algum tipo de processamento de imagem / mapeamento, então eu realmente não podia saber qual seria o resultado 'olhando' ou escrevendo casos de teste. E fiz isso em uma exibição de vetor Tektronix (persistente). Trabalho do governo ... 2 Teletipos batendo atrás de mim.
4
Pode-se adicionar, você ainda pode escrever seus testes para o código antigo após o fato. Em seguida, você pode experimentá-los em sua versão refatorada e, se isso ocorrer, faça uma pesquisa de bissecção no histórico de confirmação para encontrar o ponto em que ele começa a ser quebrado.
CodeMonkey 24/02
2
Eu sugiro fazer mais uma coisa. Ao coletar os dados de teste, colete estatísticas de cobertura de código, se possível. Você saberá como seus dados de teste descrevem o código em questão.
liori 24/02
2
@nocomprende, É engraçado que eu tenha feito exatamente isso com um código legado científico de fortran 77 na semana passada. Adicione a impressão de dados ascii a um arquivo, configure diretórios de teste com as entradas e a saída esperada, e meu caso de teste foi apenas uma diferença dos dois conjuntos de saída. Se eles não combinam caractere por caractere, eu quebrei alguma coisa. Quando o código é composto principalmente por duas sub-rotinas, cada uma com 2-3k LoC, é necessário iniciar em algum lugar.
Godric Seer
1

Um motivo importante para escrever testes de unidade é que eles documentam a API do componente de alguma forma. Não entender o objetivo do código em teste é realmente um problema aqui. A cobertura do código é outro objetivo importante, difícil de atingir sem saber quais ramificações de execução existem e como elas são acionadas.

No entanto, se for possível redefinir o estado de forma limpa (ou construir o novo objeto de teste toda vez), pode-se escrever um teste do tipo "lixeira para fora da lixeira" que apenas alimenta principalmente entrada aleatória no sistema e observa a saída.

Esses testes são difíceis de manter, pois quando falham, pode ser complexo dizer por que e quão sério é. A cobertura pode ser questionável. No entanto, eles ainda são muito melhores que nada. Quando esse teste falha, o desenvolvedor pode revisar as alterações mais recentes com mais atenção e, esperançosamente, identificar o bug.

h22
fonte
11
Qualquer tipo de informação é melhor do que voar às cegas. Eu costumava localizar erros em programas de servidor que estavam em produção, invocando o depurador em um arquivo de despejo de memória (Unix) e solicitando o rastreamento de pilha. Ele me deu o nome da função em que a falha ocorreu. Mesmo sem nenhum outro conhecimento (eu não sabia como usar esse depurador), ajudou no que de outra forma seria uma situação misteriosa e improdutível.