O teste de unidade pode ser adicionado com sucesso a um projeto de produção existente? Se sim, como e vale a pena?

140

Estou pensando em adicionar testes de unidade a um projeto existente que esteja em produção. Foi iniciado há 18 meses antes que eu pudesse realmente ver qualquer benefício do TDD (face palm) , então agora é uma solução bastante grande com vários projetos e não tenho a menor idéia por onde começar adicionando testes de unidade. O que está me fazendo considerar isso é que, ocasionalmente, um bug antigo parece ressurgir ou um bug é verificado como corrigido sem realmente ser corrigido. O teste de unidade reduziria ou evitaria a ocorrência desses problemas.

Lendo perguntas semelhantes sobre o SO, vi recomendações como iniciar no rastreador de erros e escrever um caso de teste para cada bug para impedir a regressão. No entanto, estou preocupado que eu acabe perdendo o quadro geral e acabe perdendo os testes fundamentais que teriam sido incluídos se eu tivesse usado o TDD desde o início.

Há algum processo / etapas que devem ser seguidos para garantir que as soluções existentes sejam adequadamente testadas em unidade e não apenas incorporadas? Como posso garantir que os testes sejam de boa qualidade e que não sejam apenas um caso de teste, é melhor que nenhum teste .

Então eu acho que o que eu também estou perguntando é;

  • Vale a pena o esforço para uma solução existente em produção?
  • É melhor ignorar o teste para este projeto e adicioná-lo em uma possível reescrita futura?
  • O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade?

(Obviamente, a resposta para o terceiro ponto depende inteiramente de você estar falando com a gerência ou com o desenvolvedor)


Razão da Recompensa

Adicionando uma recompensa para tentar atrair uma gama mais ampla de respostas que não apenas confirmam minha suspeita existente de que é uma coisa boa a se fazer, mas também algumas boas razões contra.

Meu objetivo é escrever essa pergunta mais tarde com prós e contras para tentar mostrar à gerência que vale a pena gastar horas trabalhando na mudança do desenvolvimento futuro do produto para TDD. Quero abordar esse desafio e desenvolver meu raciocínio sem meu próprio ponto de vista tendencioso.

djdd87
fonte
11
Ligação obrigatória ao livro Michael Feathers sobre o tema: amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/...
Mark Rushakoff
1
@ Mark - Obrigado, está no link que eu forneci. Eu esperava obter uma resposta decente sem comprar outro livro ... embora você nunca possa ter muitos livros (a menos que você queira fazer o trabalho).
precisa saber é o seguinte
1
Você realmente precisa ler este livro :) É um dos meus favoritos e realmente ajuda a entender a tensão entre refatoração e testes automatizados.
manuel aldana

Respostas:

177

Eu introduzi testes de unidade em bases de código que não possuíam anteriormente. O último grande projeto em que estive envolvido, onde o produto já estava em produção, com zero teste de unidade quando cheguei à equipe. Quando saí - dois anos depois -, tínhamos mais de 4500 testes produzindo cerca de 33% de cobertura de código em uma base de código com 230.000 + de produção LOC (aplicativo Win-Forms financeiro em tempo real). Isso pode parecer baixo, mas o resultado foi uma melhoria significativa na qualidade do código e na taxa de defeitos - além de melhorar o moral e a lucratividade.

Isso pode ser feito quando você tem um entendimento e um compromisso precisos das partes envolvidas.

Primeiro de tudo, é importante entender que o teste de unidade é uma habilidade em si. Você pode ser um programador muito produtivo de acordo com os padrões "convencionais" e ainda se esforçar para escrever testes de unidade de uma maneira que seja escalável em um projeto maior.

Além disso, e especificamente para sua situação, adicionar testes de unidade a uma base de código existente que não possui testes também é uma habilidade especializada. A menos que você ou alguém da sua equipe tenha uma experiência bem-sucedida com a introdução de testes de unidade em uma base de código existente, eu diria que a leitura do livro de Feather é um requisito (não é opcional ou é altamente recomendável).

Fazer a transição para o teste de unidade de seu código é um investimento em pessoas e habilidades, tanto quanto na qualidade da base de código. Compreender isso é muito importante em termos de mentalidade e gerenciamento de expectativas.

Agora, para seus comentários e perguntas:

No entanto, estou preocupado que acabe perdendo o quadro geral e acabe perdendo os testes fundamentais que teriam sido incluídos se eu tivesse usado o TDD desde o início.

Resposta curta: Sim, você perderá os testes e sim, eles podem não parecer inicialmente com o que teriam em uma situação de campo verde.

A resposta em nível mais profundo é esta: não importa. Você começa sem testes. Comece a adicionar testes e refatorar à medida que avança. À medida que os níveis de habilidade melhorarem, comece a elevar os padrões para todo o código recém-escrito adicionado ao seu projeto. Continue melhorando etc ...

Agora, lendo as entrelinhas aqui, tenho a impressão de que isso vem da mentalidade de "perfeição como desculpa para não agir". Uma mentalidade melhor é se concentrar na autoconfiança. Portanto, como você ainda não sabe como fazê-lo, descobrirá como proceder e preenche os espaços em branco. Portanto, não há razão para se preocupar.

Novamente, é uma habilidade. Você não pode passar de zero testes à perfeição TDD em uma abordagem de "processo" ou "passo a passo" de livros de culinária de maneira linear. Será um processo. Suas expectativas devem ser de progresso e aprimoramento graduais e incrementais. Não há pílula mágica.

A boa notícia é que, com o passar dos meses (e até anos), seu código gradualmente começará a se tornar um código "adequado", bem fatorado e testado.

Como uma nota rodapé. Você descobrirá que o principal obstáculo para a introdução de testes de unidade em uma antiga base de código é a falta de coesão e dependências excessivas. Portanto, você provavelmente descobrirá que a habilidade mais importante será como quebrar as dependências existentes e dissociar o código, em vez de escrever os próprios testes de unidade.

Há algum processo / etapas que devem ser seguidos para garantir que as soluções existentes sejam adequadamente testadas em unidade e não apenas incorporadas?

A menos que você já o tenha, configure um servidor de compilação e configure uma compilação de integração contínua que seja executada em cada check-in, incluindo todos os testes de unidade com cobertura de código.

Treine seu pessoal.

Comece em algum lugar e comece a adicionar testes enquanto progride da perspectiva do cliente (veja abaixo).

Use a cobertura do código como uma referência orientadora de quanto da sua base de código de produção está sendo testada.

O tempo de construção deve ser sempre RÁPIDO. Se o seu tempo de construção for lento, suas habilidades de teste de unidade estão atrasadas. Encontre os testes lentos e aprimore-os (desacople o código de produção e teste isoladamente). Bem escrito, você deve ser capaz de realizar vários milhares de testes de unidade e ainda concluir uma compilação em menos de 10 minutos (~ 1-poucos ms / teste é uma diretriz boa, mas muito grosseira, algumas poucas exceções podem ser aplicadas como código usando reflexão etc.) )

Inspecione e adapte.

Como posso garantir que os testes sejam de boa qualidade e que não sejam apenas um caso de teste, é melhor que nenhum teste.

Seu próprio julgamento deve ser sua principal fonte de realidade. Não há métrica que possa substituir a habilidade.

Se você não tiver essa experiência ou julgamento, considere contratar alguém que o faça.

Dois indicadores secundários aproximados são a cobertura total do código e a velocidade de construção.

Vale a pena o esforço para uma solução existente em produção?

Sim. A grande maioria do dinheiro gasto em um sistema ou solução personalizada é gasta depois que é colocada em produção. E investir em qualidade, pessoas e habilidades nunca deve estar fora de moda.

É melhor ignorar o teste para este projeto e adicioná-lo em uma possível reescrita futura?

Você teria que levar em consideração, não apenas o investimento em pessoas e habilidades, mas o mais importante, o custo total de propriedade e o tempo de vida útil esperado do sistema.

Minha resposta pessoal seria "sim, é claro" na maioria dos casos, porque eu sei que é muito melhor, mas reconheço que pode haver exceções.

O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade?

Nem. Sua abordagem deve ser adicionar testes à sua base de código enquanto você está progredindo em termos de funcionalidade.

Novamente, é um investimento em pessoas, habilidades E na qualidade da base de código e, como tal, exigirá tempo. Os membros da equipe precisam aprender como quebrar dependências, escrever testes de unidade, aprender novos hábitos, melhorar a disciplina e a conscientização da qualidade, como projetar melhor o software etc. É importante entender que, quando você começa a adicionar testes, é provável que os membros da equipe não o façam ainda têm essas habilidades no nível necessário para que essa abordagem seja bem-sucedida, portanto, interromper o progresso para gastar todo o tempo para adicionar muitos testes simplesmente não funcionará.

Além disso, adicionar testes de unidade a uma base de código existente de qualquer tamanho considerável de projeto é uma grande tarefa que exige comprometimento e persistência. Você não pode mudar algo fundamental, esperar muito aprendizado pelo caminho e pedir ao seu patrocinador que não espere nenhum ROI, interrompendo o fluxo de valor comercial. Isso não vai voar e, francamente, não deveria.

Em terceiro lugar, você deseja incutir valores sólidos de foco nos negócios em sua equipe. A qualidade nunca chega às custas do cliente e você não pode ir rápido sem qualidade. Além disso, o cliente está vivendo em um mundo em mudança, e seu trabalho é facilitar sua adaptação. O alinhamento do cliente requer qualidade e fluxo de valor comercial.

O que você está fazendo é pagar dívidas técnicas. E você está fazendo isso enquanto ainda atende a seus clientes, sempre mudando as necessidades. Gradualmente, com o pagamento da dívida, a situação melhora e é mais fácil atender melhor ao cliente e agregar mais valor. Etc. Esse momento positivo é o que você deve buscar, porque sublinha os princípios do ritmo sustentável e manterá e melhorará a moral - tanto para sua equipe de desenvolvimento, seu cliente quanto seus stakeholders.

espero que ajude

Mahol25
fonte
Reflete a mesma mentalidade que eu. Atualmente, estou em processo de apresentação desse processo / método exato para introduzir casos de teste em um aplicativo JAVA grande. :)
Darshan Joshi
3
Esta é a melhor resposta articulada sobre qualquer tópico que eu já li no Stackoverflow. Bem feito! Se você ainda não o fez, sugiro que você escreva um livro sobre sua resposta à última pergunta.
Yermo Lamers
Obrigado Yermo. Não tenho certeza se tenho tempo para escrever um livro. Mas talvez eu pudesse escrever um artigo ou dois de blog. Apenas iniciando meu novo blog, pode demorar um pouco.
precisa saber é o seguinte
2
Esse conselho de teste de unidade é um conselho geral da vida. Seriamente.
precisa saber é o seguinte
24
  • Vale a pena o esforço para uma solução existente em produção?

Sim!

  • É melhor ignorar o teste para este projeto e adicioná-lo em uma possível reescrita futura?

Não!

  • O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade?

A adição de testes (especialmente testes automatizados) facilita muito a manutenção do projeto no futuro e reduz significativamente a probabilidade de você enviar problemas estúpidos ao usuário.

Os testes a priori são aqueles que verificam se o que você acredita que a interface pública do seu código (e cada módulo nele) está funcionando da maneira que você pensa. Se puder, tente também induzir cada modo de falha isolada que seus módulos de código devem ter (observe que isso pode não ser trivial e você deve ter cuidado para não verificar com muito cuidado como as coisas falham, por exemplo, você realmente não deseja para fazer coisas como contar o número de mensagens de log produzidas em caso de falha, pois verificar se está logado é suficiente).

Em seguida, faça um teste para cada bug atual em seu banco de dados que induza exatamente o bug e que será aprovado quando o bug for corrigido. Então conserte esses bugs :-)

Custa tempo adiantado para adicionar testes, mas você é recompensado muitas vezes no back-end, pois seu código acaba sendo de qualidade muito mais alta. Isso é muito importante quando você está tentando enviar uma nova versão ou realizar manutenção.

Donal Fellows
fonte
Obrigado pela resposta elaborada. Confirmei o que eu estava sentindo. Verá que outras respostas serão postadas antes de votar e aceitará.
precisa saber é o seguinte
15

O problema com a atualização de testes de unidade é que você perceberá que não pensou em injetar uma dependência aqui ou usar uma interface lá e, em pouco tempo, estará reescrevendo todo o componente. Se você tiver tempo para fazer isso, criará uma boa rede de segurança, mas poderá ter introduzido erros sutis ao longo do caminho.

Eu estive envolvido com muitos projetos que realmente precisavam de testes de unidade desde o primeiro dia, e não há uma maneira fácil de obtê-los, exceto uma reescrita completa, que geralmente não pode ser justificada quando o código está funcionando e já está ganhando dinheiro. Recentemente, comecei a escrever scripts do PowerShell que exercitam o código de uma maneira que reproduz um defeito assim que é gerado e, em seguida, os mantemos como um conjunto de testes de regressão para novas alterações na linha. Dessa forma, você pode pelo menos começar a criar alguns testes para o aplicativo sem alterá-lo muito; no entanto, eles são mais parecidos com testes de regressão de ponta a ponta do que com testes de unidade adequados.

fletcher
fonte
Se o código estiver dividido razoavelmente bem desde o início, é fácil testá-lo. O problema ocorre quando você tem um código confuso e unido e onde os únicos pontos de teste expostos são para testes de integração completa. (Eu tenho um código como esse em que a testabilidade é quase zero devido à quantidade em que depende de outros componentes que não são fáceis de zombar e em que a implantação exige uma reinicialização do servidor. Testável ... mas difícil de executar.)
Donal Fellows
13

Eu concordo com o que quase todo mundo já disse. Adicionar testes ao código existente é valioso. Nunca discordo desse ponto, mas gostaria de acrescentar uma ressalva.

Embora a adição de testes ao código existente seja valiosa, ela tem um custo. Ele tem o custo de não criar novos recursos. Como essas duas coisas se equilibram depende inteiramente do projeto, e há várias variáveis.

  • Quanto tempo você leva para colocar todo esse código em teste? Dias? Semanas? Meses? Anos?
  • Para quem você está escrevendo este código? Pagando clientes? Um professor? Um projeto de código aberto?
  • Como é sua agenda? Você tem prazos rígidos que precisa cumprir? Você tem algum prazo?

Mais uma vez, deixe-me enfatizar, os testes são valiosos e você deve trabalhar para colocar seu código antigo em teste. Isso é realmente mais uma questão de como você a aborda. Se você pode deixar de lado tudo e colocar todo o seu código antigo em teste, faça-o. Se isso não for realista, aqui está o que você deve fazer pelo menos

  • Qualquer novo código que você escrever deve estar completamente em teste de unidade
  • Qualquer código antigo que você toque (correção de erros, extensão etc.) deve ser colocado em teste de unidade

Além disso, essa não é uma proposição de tudo ou nada. Se você tem uma equipe de, digamos, quatro pessoas, e pode cumprir seus prazos colocando uma ou duas pessoas em serviço de teste legado, faça isso de qualquer maneira.

Editar:

Meu objetivo é escrever essa pergunta mais tarde com prós e contras para tentar mostrar à gerência que vale a pena gastar horas trabalhando na mudança do desenvolvimento futuro do produto para TDD.

É como perguntar "Quais são os prós e os contras de usar o controle de origem?" ou "Quais são os prós e os contras de entrevistar pessoas antes de contratá-las?" ou "Quais são os prós e os contras da respiração?"

Às vezes, há apenas um lado no argumento. Você precisa ter testes automatizados de alguma forma para qualquer projeto de qualquer complexidade. Não, os testes não se escrevem e, sim, levará um tempo extra para que as coisas saiam da porta. Mas , a longo prazo, levará mais tempo e custará mais dinheiro para corrigir bugs após o fato do que escrever testes antecipadamente. Período. É tudo o que há para isso.

haydenmuhl
fonte
9

Quando começamos a adicionar testes, era para uma base de código de dez anos e aproximadamente milhões de linhas, com muita lógica na interface do usuário e no código de relatório.

Uma das primeiras coisas que fizemos (depois de configurar um servidor de construção contínuo) foi adicionar testes de regressão. Estes foram testes de ponta a ponta.

  • Cada suíte de teste começa inicializando o banco de dados para um estado conhecido. Na verdade, temos dezenas de conjuntos de dados de regressão que mantemos no Subversion (em um repositório separado do nosso código, devido ao tamanho). O FixtureSetUp de cada teste copia um desses conjuntos de dados de regressão em um banco de dados temporário e depois é executado a partir daí.
  • A instalação do equipamento de teste executa algum processo cujos resultados estamos interessados. (Esta etapa é opcional - existem alguns testes de regressão apenas para testar os relatórios).
  • Em seguida, cada teste executa um relatório, gera o relatório em um arquivo .csv e compara o conteúdo desse arquivo .csv com um instantâneo salvo. Esses instantâneos .csvs são armazenados no Subversion ao lado de cada conjunto de dados de regressão. Se a saída do relatório não corresponder ao instantâneo salvo, o teste falhará.

O objetivo dos testes de regressão é informar se algo muda. Isso significa que eles falham se você quebrou algo, mas também falham se você mudou algo de propósito (nesse caso, a correção é atualizar o arquivo de captura instantânea). Você não sabe que os arquivos de snapshot estão corretos - pode haver erros no sistema (e, quando você os corrige, os testes de regressão falham).

No entanto, os testes de regressão foram uma grande vitória para nós. Quase tudo no nosso sistema tem um relatório; portanto, ao passar algumas semanas testando os relatórios, conseguimos obter algum nível de cobertura em grande parte de nossa base de códigos. Escrever os testes unitários equivalentes levaria meses ou anos. (Os testes de unidade nos dariam uma cobertura muito melhor e seriam muito menos frágeis; mas prefiro ter algo agora, em vez de esperar anos pela perfeição.)

Em seguida, voltamos e começamos a adicionar testes de unidade quando corrigimos bugs, adicionamos aprimoramentos ou precisamos entender algum código. Os testes de regressão de forma alguma eliminam a necessidade de testes de unidade; eles são apenas uma rede de segurança de primeiro nível, para que você obtenha algum nível de cobertura de teste rapidamente. Em seguida, você pode começar a refatorar para quebrar dependências, para adicionar testes de unidade; e os testes de regressão fornecem um nível de confiança de que sua refatoração não está quebrando nada.

Os testes de regressão têm problemas: são lentos e há muitas razões pelas quais eles podem quebrar. Mas, pelo menos para nós, eles eram tão vale a pena. Eles detectaram incontáveis ​​bugs nos últimos cinco anos e os pegaram em poucas horas, em vez de esperar por um ciclo de controle de qualidade. Ainda temos esses testes de regressão originais, espalhados por sete máquinas diferentes de construção contínua (separadas daquela que executa os testes rápidos de unidade), e até as adicionamos de vez em quando, porque ainda temos tanto código que nossos 6.000 + testes de unidade não cobrem.

Joe White
fonte
8

É absolutamente vale a pena. Nosso aplicativo possui regras complexas de validação cruzada, e recentemente tivemos que fazer alterações significativas nas regras de negócios. Acabamos com conflitos que impediam o usuário de salvar. Percebi que levaria uma eternidade para resolvê-lo no aplicativo (leva alguns minutos apenas para chegar ao ponto em que os problemas estavam). Eu queria introduzir testes de unidade automatizados e instalar a estrutura, mas não fiz nada além de alguns testes fictícios para garantir que as coisas funcionassem. Com as novas regras de negócios em mãos, comecei a escrever testes. Os testes identificaram rapidamente as condições que causaram os conflitos e pudemos esclarecer as regras.

Se você escrever testes que abranjam a funcionalidade que está adicionando ou modificando, obterá um benefício imediato. Se você esperar uma reescrita, talvez nunca tenha testes automatizados.

Você não deve gastar muito tempo escrevendo testes para coisas existentes que já funcionam. Na maioria das vezes, você não tem uma especificação para o código existente; portanto, o principal que você está testando é sua capacidade de engenharia reversa. Por outro lado, se você quiser modificar algo, precisará cobrir essa funcionalidade com testes para saber que fez as alterações corretamente. E, é claro, para novas funcionalidades, escreva testes que falhem e implemente a funcionalidade ausente.

Hugh Brackett
fonte
6

Vou adicionar minha voz e dizer que sim, é sempre útil!

Existem algumas distinções que você deve ter em mente: caixa preta versus caixa branca e unidade vs funcional. Como as definições variam, eis o que quero dizer com estas:

  • Caixa preta = testes que são escritos sem um conhecimento especial da implementação, geralmente aparecendo nos casos extremos para garantir que as coisas aconteçam como um usuário ingênuo esperaria.
  • Caixa branca = testes escritos com o conhecimento da implementação, que geralmente tentam exercitar pontos de falha conhecidos.
  • Testes unitários = testes de unidades individuais (funções, módulos separáveis, etc). Por exemplo: verifique se a classe da matriz funciona conforme o esperado e se a função de comparação de cadeias retorna os resultados esperados para uma ampla variedade de entradas.
  • Testes funcionais = testes de todo o sistema de uma só vez. Esses testes exercitarão uma grande parte do sistema de uma só vez. Por exemplo: init, abra uma conexão, faça algumas coisas do mundo real, feche, encerre. Eu gosto de fazer uma distinção entre esses e os testes de unidade, porque eles servem a um propósito diferente.

Quando adicionei testes a um produto de remessa no final do jogo, descobri que consegui o melhor retorno possível com testes de caixa branca e funcionais . Se alguma parte do código que você conhece é especialmente frágil, escreva testes de caixa branca para cobrir os casos com problemas e ajudar a garantir que não seja quebrado da mesma maneira duas vezes. Da mesma forma, os testes funcionais do sistema inteiro são uma verificação de sanidade útil que ajuda a garantir que você nunca interrompa os 10 casos de uso mais comuns.

Os testes de caixa preta e de unidade de unidades pequenas também são úteis, mas se o seu tempo for limitado, é melhor adicioná-los mais cedo. Quando você envia, geralmente encontra (da maneira mais difícil) a maioria dos casos extremos e problemas que esses testes teriam encontrado.

Como os outros, também lembrarei as duas coisas mais importantes sobre o TDD:

  1. Criar testes é um trabalho contínuo. Isso nunca para. Você deve tentar adicionar novos testes sempre que escrever um novo código ou modificar o código existente.
  2. Sua suíte de testes nunca é infalível! Não deixe que o fato de você ter testes leve você a uma falsa sensação de segurança. Só porque ele passa no conjunto de testes não significa que está funcionando corretamente ou que você não introduziu uma regressão sutil de desempenho, etc.
Drew Thaler
fonte
4

Se vale a pena adicionar testes de unidade a um aplicativo em produção depende do custo de manutenção do aplicativo. Se o aplicativo tiver poucos bugs e solicitações de aprimoramento, talvez não valha a pena. OTOH, se o aplicativo estiver com erros ou modificado com frequência, os testes de unidade serão extremamente benéficos.

Neste ponto, lembre-se de que estou falando sobre adicionar testes de unidade seletivamente, sem tentar gerar um conjunto de testes semelhantes aos que existiriam se você tivesse praticado TDD desde o início. Portanto, em resposta à segunda metade da sua segunda pergunta: faça questão de usar o TDD em seu próximo projeto, seja um novo projeto ou uma reescrita (desculpas, mas aqui está um link para outro livro que você realmente deve ler : Software orientado a objetos em crescimento, guiado por testes )

Minha resposta para sua terceira pergunta é a mesma que a primeira: depende do contexto do seu projeto.

Incorporada à sua postagem, há uma pergunta adicional sobre como garantir que qualquer teste retro-montado seja feito corretamente . O importante a garantir é que os testes de unidade são realmente testes de unidade , e isso (na maioria das vezes) significa que os testes de retromontagem exigem refatoração do código existente para permitir o desacoplamento de suas camadas / componentes (cf. injeção de dependência; inversão de controle; stubbing); zombando). Se você não aplicar isso, seus testes se tornarão testes de integração, que são úteis, mas menos direcionados e mais frágeis que os testes de unidade verdadeiros.

Seb Rose
fonte
4

Você não menciona a linguagem de implementação, mas se em Java, você pode tentar esta abordagem:

  1. Em uma árvore de origem separada, crie testes de regressão ou 'fumaça', usando uma ferramenta para gerá-los, o que pode levar a quase 80% de cobertura. Esses testes executam todos os caminhos lógicos de código e verificam a partir desse ponto que o código ainda faz exatamente o que faz atualmente (mesmo se houver um erro). Isso fornece uma rede de segurança contra a alteração inadvertida do comportamento ao fazer a refatoração necessária para tornar o código facilmente testável por unidade manualmente.

  2. Para cada bug corrigido ou recurso adicionado a partir de agora, use uma abordagem TDD para garantir que o novo código seja projetado para ser testável e coloque esses testes em uma árvore de origem de teste normal.

  3. O código existente provavelmente também precisará ser alterado ou refatorado para torná-lo testável como parte da adição de novos recursos; seus testes de fumaça fornecerão uma rede de segurança contra regressões ou alterações sutis inadvertidas no comportamento.

  4. Ao fazer alterações (correções ou recursos) via TDD, quando concluído, é provável que o teste de fumaça associado esteja falhando. Verifique se as falhas são as esperadas devido às alterações feitas e remova o teste de fumaça menos legível, pois seu teste de unidade escrito à mão tem cobertura total desse componente aprimorado. Certifique-se de que a cobertura do seu teste não diminua, permaneça a mesma ou aumente.

  5. Ao corrigir erros, escreva um teste de unidade com falha que exponha o erro primeiro.

Chris B
fonte
3

Gostaria de começar esta resposta dizendo que o teste de unidade é realmente importante porque ajudará a prender bugs antes que eles entrem em produção.

Identifique as áreas de projetos / módulos onde os bugs foram reintroduzidos. comece com esses projetos para escrever testes. Faz perfeitamente sentido escrever testes para novas funcionalidades e para correção de bugs.

Vale a pena o esforço para uma solução existente em produção?

Sim. Você verá o efeito dos bugs caindo e a manutenção se tornando mais fácil

É melhor ignorar o teste para este projeto e adicioná-lo em uma possível reescrita futura?

Eu recomendaria começar se a partir de agora.

O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade?

Você está fazendo a pergunta errada. Definitivamente, a funcionalidade é mais importante do que qualquer outra coisa. Mas você deve perguntar se passar algumas semanas adicionando teste tornará meu sistema mais estável. Isso ajudará meu usuário final? Isso ajudará um novo desenvolvedor da equipe a entender o projeto e também garantir que ele / ela não introduza um erro devido à falta de entendimento do impacto geral de uma mudança.

PK
fonte
3

Gosto muito de refatorar a fruta baixa como resposta à pergunta de onde começar a refatoração. É uma maneira de facilitar um design melhor sem morder mais do que você pode mastigar.

Eu acho que a mesma lógica se aplica ao TDD - ou apenas aos testes de unidade: escreva os testes de que você precisa, conforme necessário; escreva testes para novo código; escreva testes para erros quando eles aparecerem. Você está preocupado em negligenciar áreas mais difíceis de alcançar da base de código, e certamente é um risco, mas como uma maneira de começar: comece! Você pode reduzir o risco no caminho com ferramentas de cobertura de código, e o risco não é (na minha opinião) tão grande assim: se você está cobrindo os bugs, cobrindo o novo código, cobrindo o código que está procurando , você está cobrindo o código que tem maior necessidade de testes.

Carl Manaster
fonte
2
  • Sim, ele é. quando você começa a adicionar novas funcionalidades, pode causar algumas modificações antigas no código e, como resultado, é uma fonte de possíveis erros.
  • (consulte o primeiro) antes de começar a adicionar novas funcionalidades, todo (ou quase) o código (idealmente) deve ser coberto por testes de unidade.
  • (veja o primeiro e o segundo) :). uma nova funcionalidade grandiosa pode "destruir" o código antigo trabalhado.
garik
fonte
2

Sim, pode: tente garantir que todo o código que você escreve a partir de agora tenha um teste em vigor.

Se o código que já está no local precisar ser modificado e puder ser testado, faça-o, mas é melhor não ser muito vigoroso ao tentar executar testes para o código estável. Esse tipo de coisa tende a ter um efeito indireto e pode sair do controle.

graham.reeds
fonte
2

Vale a pena o esforço para uma solução existente em produção?
Sim. Mas você não precisa escrever todos os testes de unidade para começar. Basta adicioná-los um por um.

É melhor ignorar o teste para este projeto e adicioná-lo em uma possível reescrita futura?
Não. Na primeira vez em que você adicionar um código que interrompa a funcionalidade, você se arrependerá.

O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade?
Para novas funcionalidades (código) é simples. Você escreve o teste de unidade primeiro e depois a funcionalidade. Para código antigo, você decide o caminho. Você não precisa ter todos os testes de unidade no lugar ... Adicione os que mais o machucam por não ter ... O tempo (e os erros) dirão em qual deles você deve se concentrar;)

udo
fonte
2

Atualizar

Seis anos após a resposta original, tenho uma opinião um pouco diferente.

Eu acho que faz sentido adicionar testes de unidade a todos os novos códigos que você escreve - e refatorar os locais onde você faz alterações para torná-los testáveis.

Escrever testes de uma só vez para todo o código existente não ajudará - mas não escrever testes para o novo código que você escreve (ou áreas que você modifica) também não faz sentido. Adicionar testes à medida que você refatorar / adicionar itens é provavelmente a melhor maneira de adicionar testes e tornar o código mais sustentável em um projeto existente sem testes.

Resposta anterior

Vou levantar algumas sobrancelhas aqui :)

Antes de mais nada, qual é o seu projeto - se é um compilador, uma linguagem, uma estrutura ou qualquer outra coisa que não muda funcionalmente por muito tempo, então acho absolutamente fantástico adicionar testes de unidade.

No entanto, se você estiver trabalhando em um aplicativo que provavelmente exigirá alterações na funcionalidade (devido à alteração dos requisitos), não faz sentido fazer esse esforço extra.

Por quê?

  1. Os testes de unidade abrangem apenas os testes de código - se o código faz o que foi projetado - não é um substituto para o teste manual que precisa ser feito de qualquer maneira (para descobrir bugs funcionais, problemas de usabilidade e todos os outros tipos de problemas)

  2. Testes unitários custam tempo! Agora, de onde eu venho, essa é uma mercadoria preciosa - e os negócios geralmente escolhem melhor funcionalidade em um conjunto completo de testes.

  3. Se seu aplicativo for remotamente útil para os usuários, eles solicitarão alterações - assim você terá versões que farão coisas melhores, mais rápidas e provavelmente farão coisas novas - também pode haver muita refatoração à medida que seu código aumenta. Manter um conjunto de testes de unidade adulto em um ambiente dinâmico é uma dor de cabeça.

  4. Os testes de unidade não afetarão a qualidade percebida do seu produto - a qualidade que o usuário vê. Claro, seus métodos podem funcionar exatamente como no dia 1, a interface entre a camada de apresentação e a de negócios pode ser pura - mas adivinhem? O usuário não se importa! Obtenha alguns testadores reais para testar seu aplicativo. E, na maioria das vezes, esses métodos e interfaces precisam mudar de qualquer maneira, mais cedo ou mais tarde.

O que será mais benéfico; passando algumas semanas adicionando testes ou algumas semanas adicionando funcionalidade? - Há muitas coisas que você pode fazer melhor do que escrever testes - Escreva novas funcionalidades, melhore o desempenho, melhore a usabilidade, escreva melhores manuais de ajuda, solucione bugs pendentes, etc.

Agora, não me entenda mal - se você está absolutamente certo de que as coisas não vão mudar nos próximos 100 anos, vá em frente, se nocauteie e escreva esses testes. Os testes automatizados também são uma ótima idéia para APIs, onde você absolutamente não deseja quebrar o código de terceiros. Em qualquer outro lugar, é apenas algo que me faz enviar mais tarde!

Roopesh Shenoy
fonte
E peço que você leia isto - joelonsoftware.com/items/2009/01/31.html
Roopesh Shenoy 19/08/10
Você prefere 'corrigir bugs pendentes' ou impedir que eles surjam? O teste de unidade adequado economiza tempo, minimizando a quantidade de tempo gasto na correção de erros.
Ihor Kaharlichenko
Isso é um mito. Se você está me dizendo que os testes de unidade automatizados substituem os testes manuais, você está seriamente, seriamente enganado. E o que os testadores manuais registram, se não os erros?
Roopesh Shenoy
E sim, não me entenda mal - não estou dizendo que os testes de unidade são um desperdício absoluto - o ponto é considerar o tempo necessário para escrevê-los e as razões pelas quais eles podem ter que mudar quando você muda seu produto, eles realmente pagam. Para mim, tentei ambos os lados e a resposta foi não, eles não pagam rápido o suficiente.
Roopesh Shenoy 22/08/10
1

É improvável que você tenha uma cobertura de teste significativa; portanto, você deve ser tático sobre onde adicionar testes:

  • Como você mencionou, quando você encontra um bug, é um bom momento para escrever um teste (para reproduzi-lo) e, em seguida, corrigi-lo. Se você vir o teste reproduzir o bug, pode ter certeza de que é um bom teste. Dado que uma porção tão grande de erros são regressões (50%?), Quase sempre vale a pena escrever testes de regressão.
  • Quando você mergulha em uma área do código para modificá-la, é um bom momento para escrever testes em torno dela. Dependendo da natureza do código, testes diferentes são apropriados. Um bom conjunto de conselhos é encontrado aqui .

OTOH, não vale a pena ficar sentado escrevendo testes em torno do código com o qual as pessoas estão felizes - especialmente se ninguém for modificá-lo. Apenas não agrega valor (exceto, talvez, a compreensão do comportamento do sistema).

Boa sorte!

ndp
fonte
1

Se eu estivesse no seu lugar, provavelmente adotaria uma abordagem de fora para dentro, começando com testes funcionais que exercitam todo o sistema. Eu tentaria re-documentar os requisitos do sistema usando uma linguagem de especificação BDD como o RSpec e, em seguida, escrever testes para verificar esses requisitos automatizando a interface do usuário.

Em seguida, desenvolvia o desenvolvimento orientado a defeitos para erros descobertos recentemente, escrevendo testes de unidade para reproduzir os problemas e trabalhando nos erros até que os testes passassem.

Para novos recursos, eu continuaria com a abordagem de fora para dentro: comece com os recursos documentados no RSpec e verificados automatizando a interface do usuário (que obviamente falhará inicialmente) e, em seguida, adicione testes de unidade mais detalhados à medida que a implementação avança.

Não sou especialista no processo, mas, com a pouca experiência que tenho, posso dizer que o BDD por meio de teste automatizado da interface do usuário não é fácil, mas acho que vale a pena o esforço e provavelmente traria o maior benefício no seu caso.

Mhmmd
fonte
1

Eu não sou um especialista em TDD experiente, por qualquer meio, mas é claro que eu diria que é incrivelmente importante fazer o teste de unidade o máximo que puder. Como o código já está no lugar, eu começaria colocando algum tipo de automação de teste de unidade. Eu uso o TeamCity para exercitar todos os testes em meus projetos e fornece um bom resumo de como os componentes foram.

Com isso, eu passaria para os componentes realmente críticos da lógica de negócios que não podem falhar. No meu caso, existem alguns problemas básicos de trigometria que precisam ser resolvidos para várias entradas, então eu testei isso. A razão pela qual faço isso é que, quando estou queimando o óleo da meia-noite, é muito fácil perder tempo pesquisando profundamente o código que realmente não precisa ser tocado, porque você sabe que eles são testados para todas as entradas possíveis (no meu caso, há um número finito de entradas).

Ok, agora esperamos que você se sinta melhor sobre essas peças críticas. Em vez de me sentar e bater em todos os testes, eu os atacava quando eles apareciam. Se você acertar um bug que é realmente uma PITA para corrigir, escreva os testes de unidade e tire-os do caminho.

Há casos em que você descobre que o teste é difícil porque não pode instanciar uma classe específica a partir do teste; portanto, é necessário zombar dele. Ah, mas talvez você não possa zombar facilmente porque não gravou em uma interface. Tomo esses cenários de "gritos" como uma oportunidade para implementar a interface, porque, bem, é uma coisa boa.

A partir daí, eu obteria seu servidor de compilação ou qualquer automação existente configurada com uma ferramenta de cobertura de código. Eles criam gráficos de barras desagradáveis ​​com grandes zonas vermelhas nas quais você tem pouca cobertura. Agora 100% de cobertura não é sua meta, nem 100% de cobertura significa necessariamente que seu código é à prova de balas, mas a barra vermelha definitivamente me motiva quando tenho tempo livre. :)

Dave
fonte
1

Há tantas respostas boas que não repetirei o conteúdo delas. Eu verifiquei seu perfil e parece que você é desenvolvedor C # .NET. Por isso, estou adicionando referência ao projeto Microsoft PEX e Moles , que pode ajudá-lo com a geração automática de testes de unidade para código legado. Eu sei que a geração automática não é a melhor maneira, mas pelo menos é a maneira de começar. Confira este artigo muito interessante da revista MSDN sobre o uso do PEX para código legado .

Ladislav Mrnka
fonte
1

Sugiro a leitura de um artigo brilhante de um engenheiro da TopTal, que explica por onde começar a adicionar testes: ele contém muita matemática, mas a idéia básica é:

1) Meça o acoplamento aferente (CA) do seu código (quanto uma classe é usada por outras classes, ou seja, quebrá-lo causaria danos generalizados)

2) Meça a complexidade ciclomática (CC) do seu código (maior complexidade = maior alteração de quebra)

Você precisa identificar classes com alta CA e CC, ou seja, tem uma função f (CA, CC) e as classes com as menores diferenças entre as duas métricas devem receber a maior prioridade para a cobertura do teste.

Por quê? Porque uma CA alta, mas classes CC muito baixas, são muito importantes, mas dificilmente quebram. Por outro lado, baixa CA, mas alto CC provavelmente quebrará, mas causará menos danos. Então você quer se equilibrar.

Andrejs
fonte
0

Depende ...
É ótimo ter testes de unidade, mas você precisa considerar quem são seus usuários e o que eles desejam tolerar para obter um produto mais livre de erros. Inevitavelmente, refatorando seu código que atualmente não possui testes de unidade, você apresentará bugs e muitos usuários acharão difícil entender que você está tornando o produto temporariamente mais defeituoso para torná-lo menos defeituoso a longo prazo. Em última análise, são os usuários que terão a palavra final ...

trendl
fonte
-2

Sim. Não. Adicionando testes.

Ir para uma abordagem mais TDD realmente informará melhor seus esforços para adicionar novas funcionalidades e facilitar o teste de regressão. Confira!

indra
fonte