Como escrevo testes de unidade para código legado (que não entendo)?

8

frente

Li várias coisas antes de fazer essa pergunta, incluindo muitas questões relevantes aqui no SE:

No entanto, não posso deixar de sentir que a coceira ainda não foi arranhada depois de ler para obter ajuda.


TL; DR

Como escrevo testes de unidade para código legado que não consigo executar, simular, ler ou entender facilmente? Quais testes de regressão são úteis para um componente que provavelmente funciona como planejado?


A figura inteira

Eu sou uma estagiária de verão de novo quando estou entrando na faculdade. Minha tarefa envolve estes requisitos:

  1. Para um produto específico, avalie se nossa equipe de software pode atualizar sua versão IDE e JUnit sem perder a compatibilidade com seus projetos existentes.
  2. Desenvolva testes de unidade para algum componente no código Java existente (em grande parte não é Java). Queremos convencer a equipe de software de que o teste de unidade e o TDD são ferramentas inestimáveis ​​que eles deveriam usar. (No momento, há 0% de cobertura de código.)
  3. De alguma forma, termine os dias de codificação de cowboys para um sistema crítico.

Após obter uma cópia do código-fonte, tentei compilá-lo e executá-lo, para entender o que esse produto faz e como ele funciona. Não pude. Perguntei aos meus supervisores como eu faço e recebi uma nova máquina autônoma capaz de construí-la, incluindo os scripts de construção que realmente funcionam. Isso também não funcionou porque, como deveriam, o código de produção é executado apenas no sistema incorporado para o qual foi projetado. No entanto, eles têm um simulador para esse fim, então eles obtiveram o simulador e o colocaram nesta máquina para mim. O simulador também não funcionou. Em vez disso, finalmente recebi uma impressão de uma GUI para uma tela específica. Eles também não têm comentários de código em nenhum lugar no Java LOC de mais de 700.000, tornando ainda mais difícil de entender. Além disso, houve problemas ao avaliar se seus projetos eram ou não compatíveis com os IDEs mais novos. Particularmente, o código deles não foi carregado corretamente na versão IDE que eles usam.

Meu inventário está assim:

  • NetBeans 8, 9, 10, 11
  • JUnit 4, 5
  • Seu código-fonte para um produto específico (inclui mais de 700.000 Java LOC)
  • Praticamente nenhum comentário de código (ocasionalmente uma assinatura)
  • Nenhum teste existente
  • Uma foto física de uma janela da GUI
  • Um documento de design de software (109 p.) Que não discute o componente na imagem

Eu pelo menos tenho o suficiente para escrever teoricamente testes que podem ser executados. Então, eu tentei um teste básico de unidade nesse componente. No entanto, não consegui inicializar os objetos que ele possuía como dependências, incluindo modelos, gerenciadores e conexões com o banco de dados. Como não tenho muita experiência em JUnit além dos testes básicos de unidade, siga-me na próxima seção.


O que aprendi com minha leitura

  1. Zombando: Se eu escrever um teste de unidade, provavelmente precisará ter variáveis ​​simuladas para dependências de produção nas quais não consigo inicializar facilmente setUp.
  2. Todos aqui liberalmente sugerem o livro "Trabalhando efetivamente com o código legado", de Michael Feathers.
  3. Provavelmente, os testes de regressão são um bom ponto de partida. Acho que não tenho armas suficientes para tentar o teste de integração, e os testes de regressão proporcionariam gratificação mais instantânea à nossa equipe de software. No entanto, não tenho acesso aos bugs conhecidos deles; mas eu poderia perguntar.

E agora uma tentativa de articular a incerteza que ainda tenho como questão. Essencialmente, não entendo como parte da escrita desses testes. Supondo que não receba nenhuma orientação adicional de meus supervisores (provavelmente), é da minha responsabilidade não apenas aprender o que esse componente faz, mas decidir quais testes são realmente úteis como testes de regressão.

Como profissionais que trabalharam com projetos como esse por mais tempo do que eu, você pode oferecer alguma orientação sobre como escrever testes de unidade nesse tipo de situação?

Joshua Detwiler
fonte
12
How do I write unit tests for legacy code that I can't build, run, simulate, read about, or otherwise understand?Você não pode. Você precisa pelo menos saber qual é a saída esperada para uma determinada entrada.
Robert Harvey
1
Você já leu o livro de Michael Feathers?
Robert Harvey
@RobertHarvey É uma hipérbole leve. Obviamente, posso reservar um tempo para ler o código para entendê-lo, mas esse é geralmente o último recurso para algo tão grande. E não, ainda não, pois acabei de encontrar o livro hoje de manhã.
Joshua Detwiler
Se você possui um código legado que não pode nem criar, tem certeza de que possui a versão correta do código? Você precisa resolver isso primeiro antes de se preocupar com testes automatizados - o código que você está tentando criar é o mesmo que seus usuários estão executando? O primeiro passo de tudo isso se você tivesse um sistema em execução e funcionando seria tentar entendê-lo do ponto de vista do usuário; ele pode ajudar a ler a documentação do usuário e talvez olhar para os dados do usuário, se possível, tentar descobrir o que ele faz
Ben Cottrell
2
Se os dados do sensor são cruciais para a execução, zombar dos sensores (ou quaisquer que sejam as dependências externas) parece um bom lugar para começar.
JimmyJames 19/06/19

Respostas:

10

Para uma primeira aproximação, as partes interessadas de um conjunto de testes são os desenvolvedores / mantenedores de código. Você vai precisar de um pouco do tempo deles. Insista nisso.

Pergunte a eles sobre os problemas que estão enfrentando.
Pergunte a eles sobre os erros que eles corrigiram recentemente (nos últimos dois anos, supondo que esse seja um projeto longo e lento).

Tenho a impressão de que você não espera que eles sejam amigáveis ​​ao seu trabalho; Talvez você esteja certo. Mas não acho que você possa criar algo útil sem a ajuda deles.

Eles vão segurar sua mão escrevendo o primeiro teste. Talvez você esteja arrastando-os, mas de mãos dadas de qualquer maneira.

Depois de fazer um teste, espero que fique mais claro como escrever o segundo. Se você conseguiu criar qualquer tipo de relacionamento, certamente será mais claro.

ShapeOfMatter
fonte
Obrigado pela resposta! Você também pode dar uma olhada na segunda metade da minha pergunta? Minha primeira metade foi um pouco duplicada das perguntas que vinculei, e eu estava principalmente interessado na outra parte da minha pergunta por causa disso.
Joshua Detwiler
Não sei como posso ser útil. Você está tendo problemas para entender como a estrutura de teste funciona? Você está querendo saber o que testar? Para isso, os testes de regressão são uma boa opção: qual teste teria impedido que esse bug que corrigimos fosse pressionado, se tivéssemos sido tão previdentes ?
ShapeOfMatter
Era mais "Quais testes de regressão são úteis para um componente que provavelmente funciona como pretendido?" Por exemplo, se eu devo criar testes úteis, o que é útil ser totalmente cego sobre o que funciona, o que não funciona e como funciona? Eu existo no vácuo à parte da equipe de software e as pessoas que me deram essa tarefa existem no vácuo à parte de nós dois.
Joshua Detwiler
Ainda tenho certeza de que você precisa insistir em entrar na mesma sala que as pessoas que escrevem o software, idealmente por alguns dias e provavelmente mais de uma vez. Posso me relacionar com situações de trabalho absurdas impostas, mas em algum momento um ou precisa de trabalho de um risco ou aceitar que você é apenas um banco-aquecedor, eu acho
ShapeOfMatter
Em relação aos testes de regressão: não é conceitualmente diferente de nenhum outro teste: o que eu gostaria que o programa fizesse (ou não fizesse) antes de reivindicar a um colega de trabalho que estava trabalhando? Se houver registros de novos recursos ou bugs recentes, esses são bons lugares para começar. Ou simplesmente examine a documentação que você possui e escolha algo que pareça testável. Java digitou funções, o que é bom. Uma assinatura de tipo claro pode dizer muito sobre o que esperar de uma função (e pode ser pensada como um tipo de teste de unidade em si). Verificar o comportamento NULL / string vazia / max-int também pode ser bom.
ShapeOfMatter
3

Eu vou assumir que em algum momento você pode obter o código para pelo menos compilar. Se você não consegue chegar tão longe, está na missão de um tolo.


A falta de requisitos, especificações ou capturas de tela adequados não é um bloqueador para a realização de testes. Contanto que você possa ler o código fonte, poderá escrever testes.

Se você tiver permissão para refatorar a base de código para isolar coisas como conexões de banco de dados por trás de sua própria interface, torna-se possível escrever alguns testes de unidade de caixa preta - basicamente escreva testes para lançar alguma entrada em um método e afirmar seu comportamento ou saída. Faça testes cobrindo cada linha de código em um método e peça a um dos membros seniores da equipe que revise seus testes.

Se você não tiver permissão para refatorar a base de código para escrever testes de unidade, testes de integração completa ou testes de automação da interface do usuário são sua única opção. Mesmo assim, o teste da caixa preta é sua melhor estratégia - insira algumas informações na interface do usuário e veja como elas reagem. Faça suas afirmações. Peça a um membro sênior da equipe que revise seus testes.

Em algum momento, você terá testes automatizados suficientes para começar a refatorar a base de código com confiança para introduzir testes de unidade. Os testes de interface do usuário garantem que os principais casos de uso de negócios funcionem e, em seguida, você pode adaptar uma arquitetura propícia ao teste de nível de unidade ou componente.

Outro benefício dos testes de interface do usuário é que você pode construir uma reputação com sua equipe que entende a base de código, que por sua vez os torna mais abertos para a introdução de alterações, porque a prova está no pudim. E você terá feito pudim escrevendo testes de aprovação.

Em resumo:

  • Escreva testes de caixa preta como testes de unidade ou de interface do usuário automatizados
  • Peça aos membros seniores que revisem seus testes

Você ficaria surpreso com a rapidez com que pode aprender a visão geral de um aplicativo de 700.000 linhas

Greg Burghardt
fonte
1

Com base na descrição do problema e nos seus comentários, acho que o melhor que você pode fazer é começar com a API Java e tentar criar um único teste de unidade em torno de um método isolado.

Sem acesso ao código, só posso fornecer orientações limitadas, mas procuraria algo que: a) não tenha dependências; b) não faça alterações de estado. Por exemplo. digamos que exista um método que aceite um valor e verifique se ele cai em um determinado intervalo. Se você não conseguir encontrar algo sem dependências, tente algo que recupere um valor de uma dependência e tente zombar dele.

Depois de encontrar algo pequeno como esse, você pode começar a criar testes. Se o método testar um valor positivo, passe negativo e certifique-se de que ele seja capturado etc. O problema aqui é que talvez você não saiba ao certo qual é o comportamento correto. Você pode precisar pedir ajuda ou pesquisar na documentação para isso.

É improvável que você vá muito longe com isso. A realidade é que escrever código para que possa ser testado em unidade é uma arte para si. O código que não foi escrito com isso em mente pode ser difícil ou impossível de testar na unidade.

Outra opção que pode ser implementada com mais facilidade é o teste de compatibilidade binária. Ou seja, você captura as entradas e saídas de um sistema e, para testar, alimenta essas mesmas entradas e compara as saídas. Isso não informa se o código está certo ou errado para começar, mas pode ajudar a detectar erros de regressão em que as coisas mudaram involuntariamente ao fazer alguma outra modificação. Você precisará executar o aplicativo inteiro para que isso aconteça.

JimmyJames
fonte