Como melhorar drasticamente a cobertura do código?

21

Estou encarregado de obter um aplicativo herdado em teste de unidade. Primeiro, alguns antecedentes sobre o aplicativo: é uma base de código RCP LOC Java de 600k com esses grandes problemas

  • duplicação maciça de código
  • sem encapsulamento, a maioria dos dados privados pode ser acessada de fora, alguns dos dados de negócios também são usados ​​como singletons, para que não sejam apenas alteráveis ​​de fora, mas também de qualquer lugar.
  • sem abstrações (por exemplo, nenhum modelo de negócios, os dados corporativos são armazenados em Object [] e double [] []), portanto, não há OO.

Existe um bom conjunto de testes de regressão e uma equipe eficiente de controle de qualidade está testando e localizando bugs. Conheço as técnicas de como testá-lo em livros clássicos, como Michael Feathers, mas isso é muito lento. Como existe um sistema de teste de regressão em funcionamento, não tenho medo de refatorar agressivamente o sistema para permitir a gravação de testes de unidade.

Como devo começar a atacar o problema para obter alguma cobertura rapidamente , para poder mostrar progresso ao gerenciamento (e de fato começar a ganhar com a rede de segurança dos testes JUnit)? Não quero empregar ferramentas para gerar conjuntos de testes de regressão, por exemplo, AgitarOne, porque esses testes não testam se algo está correto.

Peter Kofler
fonte
Por que não criar os testes de regressão automaticamente e verificar cada um individualmente? Tem que ser mais rápido do que escrevê-los todos à mão.
Robert Harvey
Parece um pouco engraçado chamar qualquer coisa escrita em legado de Java, mas concordou que certamente é legado. Você mencionou que não tem medo de refatorar o sistema para permitir que os testes de unidade sejam gravados, mas você não deve escrever os testes de unidade no sistema como está antes de tentar refatorar? Então sua refatoração pode ser executada nos mesmos testes de unidade para garantir que nada seja quebrado?
precisa saber é o seguinte
1
@dodgy_coder Geralmente concordo, mas espero que o controle de qualidade tradicional que funcione com eficiência me proteja por algum tempo.
precisa
1
@dodgy_coder Michael C. Feathers, autor de Trabalhando efetivamente com o código herdado define o código herdado como "código sem testes". Serve como uma definição útil.
StuperUser

Respostas:

10

Eu acredito que existem dois eixos principais ao longo dos quais o código pode ser colocado quando se trata de introduzir testes de unidade: A) quão testável é o código? e B) quão estável é (isto é, com que urgência é necessário testes)? Olhando apenas para os extremos, isso gera 4 categorias:

  1. Código fácil de testar e quebradiço
  2. Código fácil de testar e estável
  3. Código difícil de testar e quebradiço
  4. Código difícil de testar e estável

A categoria 1 é o local óbvio para começar, onde você pode obter muitos benefícios com relativamente pouco trabalho. A categoria 2 permite que você melhore rapidamente sua estatística de cobertura (boa para o moral) e obtenha mais experiência com a base de código, enquanto a categoria 3 é um trabalho mais (muitas vezes frustrante), mas também gera mais benefícios. O que você deve fazer primeiro depende da importância das estatísticas de moral e cobertura para você. Provavelmente não vale a pena incomodar a categoria 4.

Michael Borgwardt
fonte
Excelente. Eu tenho uma idéia de como determinar se é fácil verificar por análise estática, por exemplo, contagem de dependências / Testability Explorer. Mas como eu poderia determinar se o código é quebradiço? Não consigo combinar os defeitos com unidades específicas (por exemplo, aulas) e, se for o número 3, é claro (aulas de deus / singletons). Então, talvez o número de check-ins (os pontos de acesso)?
precisa
1
@ Peter Kofler: confirmar pontos de acesso é uma boa idéia, mas a fonte mais valiosa desse tipo de conhecimento seria desenvolvedores que trabalharam com o código.
precisa
1
@ Peter - como Michael disse, os desenvolvedores que trabalharam com o código. Qualquer pessoa que tenha trabalhado com uma grande base de código por um período razoável de tempo saberá quais partes dele cheiram. Ou, se a coisa toda cheira, que partes realmente cheiram .
precisa saber é o seguinte
15

Tenho muita experiência trabalhando em sistemas legados (embora não seja Java), muito maiores que isso. Eu odeio ser portador de más notícias, seu problema é do tamanho de você. Eu suspeito que você subestimou isso.

Adicionar testes de regressão ao código legado é um processo lento e caro. Muitos requisitos não estão bem documentados - uma correção de bug aqui, um patch ali e, antes que você perceba, o software define seu próprio comportamento. Não ter testes significa que a implementação é tudo o que existe, não há testes para "desafiar" os requisitos implícitos implementados no código.

Se você tentar obter cobertura rapidamente, é provável que apresse o trabalho, assar pela metade e falhará. Os testes fornecerão cobertura parcial das coisas óbvias e pouca ou nenhuma cobertura dos problemas reais. Você convencerá os próprios gerentes que está tentando vender para que o teste unitário não vale a pena, que é apenas mais uma bala de prata que não funciona.

IMHO, a melhor abordagem é direcionar seus testes. Use métricas, pressentimentos e relatórios de log de defeitos para identificar 1% ou 10% do código que produz mais problemas. Bata nesses módulos com força e ignore o resto. Não tente fazer muito, menos é mais.

Um objetivo realista é "Desde que implementamos o UT, a inserção de defeitos nos módulos em teste caiu para x% daqueles que não estão sob o UT" (idealmente x é um número <100).

mattnz
fonte
+1, você não pode testar algo efetivamente sem um padrão mais forte do que o código.
dan_waterworth
Eu sei e concordo. A diferença é que temos teste, teste de regressão tradicional trabalhando com o controle de qualidade, para que haja algum tipo de rede de segurança. Segundo, sou profundamente a favor dos testes de unidade, por isso definitivamente não será outra coisa que não funcionou. Um bom ponto sobre o que alvejar primeiro. Obrigado.
precisa
1
e não se esqueça de que o simples objetivo de "cobertura" não melhorará a qualidade, pois você ficará preso em um monte de testes imperfeitos e triviais (e testes para códigos triviais que não precisam de testes explícitos, mas são adicionados apenas para aumentar a cobertura). Você acabará criando testes para agradar a ferramenta de cobertura, não porque eles sejam úteis, e possivelmente alterará o próprio código para aumentar a cobertura do teste sem escrever testes (como cortar comentários e definições de variáveis, algumas ferramentas de cobertura chamará código descoberto).
jwenting
2

Lembro-me disso dizendo sobre não me preocupar com a porta do celeiro quando o cavalo já disparou.

A realidade é que realmente não existe uma maneira econômica de obter uma boa cobertura de teste para um sistema legado, certamente não em um curto espaço de tempo. Como MattNz mencionou, será um processo muito demorado e, por fim, caro ao extremo. Meu instinto me diz que, se você tentar fazer algo para impressionar o gerenciamento, provavelmente criará um novo pesadelo de manutenção por tentar mostrar muito rapidamente, sem entender completamente os requisitos que você está tentando testar.

Realisticamente, uma vez que você já tenha escrito o código, é quase tarde demais para escrever os testes de forma eficaz, sem o risco de perder algo vital. Por outro lado, você poderia dizer que alguns testes são melhores que nenhum, mas, se for esse o caso, os próprios testes precisam mostrar que agregam valor ao sistema como um todo.

Minha sugestão seria examinar as áreas-chave em que você sente que algo está "quebrado". Com isso, quero dizer que poderia ser um código terrivelmente ineficiente, ou que você pode demonstrar que era anteriormente caro manter isso. Documente os problemas e use-o como ponto de partida para introduzir um nível de teste que o ajude a melhorar o sistema, sem iniciar um grande esforço de reengenharia. A idéia aqui é evitar brincar com os testes e, em vez disso, introduzir testes para ajudá-lo a resolver problemas específicos. Após um período de tempo, verifique se você pode medir e distinguir entre o custo anterior da manutenção dessa seção do código e os esforços atuais com as correções aplicadas nos testes de suporte.

É importante lembrar que a gerência está mais interessada no custo / benefício e em como isso afeta diretamente seus clientes e, finalmente, seu orçamento. Eles nunca estão interessados ​​em fazer algo simplesmente porque é a melhor coisa a fazer, a menos que você possa provar que isso lhes proporcionará um benefício que é do seu interesse. Se você conseguir mostrar que está melhorando o sistema e obtendo uma boa cobertura de teste para o trabalho que está realizando atualmente, é mais provável que a gerência veja isso como uma aplicação eficiente de seus esforços. Isso poderia permitir que você argumentasse sobre a possibilidade de estender seus esforços para outras áreas-chave, sem exigir um congelamento completo do desenvolvimento do produto ou, pior ainda, o quase impossível argumentar para reescrever!

S.Robins
fonte
1

Uma maneira de melhorar a cobertura é escrever mais testes.

Outra maneira é reduzir a redundância no seu código, de forma que os testes existentes efetivamente abranjam o código redundante não coberto atualmente.

Imagine que você tem 3 blocos de código, a, bec, onde b 'é uma duplicata (cópia exata ou quase inexistente) de B, e que você tem cobertura em aeb, mas não em b' com o teste T.

Se refatorar a base de código para eliminar b 'extraindo a semelhança de b e b' como B, a base de código agora se parecerá com a, b0, B, b'0, com b0 contendo o código não compartilhado com b'0 e vice- versa, e b0 e b'0 são muito menores que B e invocam / usam B.

Agora, a funcionalidade do programa não mudou e nem o teste T, para que possamos executar o T novamente e esperar que ele seja aprovado. O código agora coberto é a, b0 e B, mas não b'0. A base de código ficou menor (b'0 é menor que b '!) E ainda cobrimos o que cobrimos originalmente. Nossa taxa de cobertura aumentou.

Para fazer isso, você precisa encontrar b, b 'e, idealmente, B para ativar sua refatoração. Nossa ferramenta CloneDR pode fazer isso para muitas linguagens, especialmente para Java. Você diz que sua base de código possui muitos códigos duplicados; essa pode ser uma boa maneira de lidar com isso em seu benefício.

Estranhamente, o ato de encontrar b e b 'geralmente aumenta seu vocabulário sobre as idéias abstratas que o programa implementa. Enquanto a ferramenta não tem idéia do que b e b 'fazem, o próprio ato de isolá-los do código, permitindo um foco simples no conteúdo de b e b', geralmente dá aos programadores uma idéia muito boa de qual abstração B_abstract o código clonado implementa . Portanto, isso também melhora sua compreensão do código. Dê um bom nome a B ao abstactá-lo e obterá melhor cobertura de teste e um programa mais sustentável.

Ira Baxter
fonte