Teste de unidade C ++: O que testar?

20

TL; DR

Escrever testes bons e úteis é difícil e tem um alto custo em C ++. Os desenvolvedores experientes podem compartilhar sua lógica sobre o que e quando testar?

Longa história

Eu costumava fazer desenvolvimento orientado a testes, toda a minha equipe, mas não funcionou bem para nós. Temos muitos testes, mas eles nunca parecem cobrir os casos em que temos erros e regressões reais - o que geralmente ocorre quando as unidades estão interagindo, e não por seu comportamento isolado.

Geralmente, é tão difícil de testar no nível da unidade que paramos de fazer TDD (exceto para componentes onde realmente acelera o desenvolvimento) e, em vez disso, investimos mais tempo aumentando a cobertura do teste de integração. Enquanto os pequenos testes de unidade nunca detectaram bugs reais e eram basicamente apenas despesas gerais de manutenção, os testes de integração realmente valeram o esforço.

Agora, herdei um novo projeto e estou pensando em como testá-lo. Como é um aplicativo C ++ / OpenGL nativo, os testes de integração não são realmente uma opção. Mas o teste de unidade em C ++ é um pouco mais difícil do que em Java (você precisa fazer coisas explicitamentevirtual ), e o programa não é muito orientado a objetos, então não posso zombar / remover algumas coisas.

Eu não quero desmembrar e otimizar a coisa toda, apenas para escrever alguns testes em prol de escrever testes. Então, eu estou lhe perguntando: para que devo escrever testes? por exemplo:

  • Funções / classes que espero mudar com frequência?
  • Funções / classes que são mais difíceis de testar manualmente?
  • Funções / classes que já são fáceis de testar?

Comecei a investigar algumas bases de código C ++ respeitosas para ver como eles fazem os testes. No momento, estou analisando o código-fonte do Chromium, mas acho difícil extrair a lógica de teste do código. Se alguém tiver um bom exemplo ou publicação de como usuários populares de C ++ (membros do comitê, autores de livros, Google, Facebook, Microsoft, ...) abordam isso, isso seria mais útil.

Atualizar

Eu procurei neste site e na Web desde que escrevi isso. Encontrou algumas coisas boas:

Infelizmente, todos esses são bastante centrados em Java / C #. Escrever muitos testes em Java / C # não é um grande problema; portanto, o benefício geralmente supera os custos.

Mas, como escrevi acima, é mais difícil em C ++. Especialmente se sua base de código não é tão OO, você precisa estragar tudo para obter uma boa cobertura de teste de unidade. Por exemplo: O aplicativo que herdei tem umGraphics espaço para nome que é uma camada fina acima do OpenGL. Para testar qualquer uma das entidades - que todas usam suas funções diretamente - eu teria que transformar isso em uma interface e uma classe e injetar em todas as entidades. Esse é apenas um exemplo.

Portanto, ao responder a essa pergunta, lembre-se de que preciso fazer um grande investimento para escrever testes.

futlib
fonte
3
+1 pela dificuldade em testar a unidade C ++. Se o seu teste de unidade exigir que você altere o código, não faça.
DPD
2
@DPD: Não tenho tanta certeza, e se algo realmente valer a pena testar? Na base de código atual, dificilmente posso testar qualquer coisa no código de simulação, porque tudo chama diretamente as funções gráficas e não posso zombar / stub delas. Tudo o que posso testar agora são funções utilitárias. Mas eu concordo, mudar o código para torná-lo "testável" parece ... errado. Os defensores do TDD costumam dizer que isso tornará todo o seu código melhor de todas as maneiras imagináveis, mas eu discordo humildemente. Nem tudo precisa de uma interface e várias implementações.
futlib 19/07/12
Deixe-me dar um exemplo recente: passei um dia inteiro tentando testar uma função única (escrita em C ++ / CLI) e a ferramenta de teste MS Test sempre falhava nesse teste. Parecia ter algum problema com referências simples ao CPP. Em vez disso, apenas testei a saída de sua função de chamada e ela funcionou bem. Eu desperdicei um dia inteiro para UT uma função. Isso foi uma perda de tempo precioso. Também não consegui nenhuma ferramenta de rascunho adequada às minhas necessidades. Eu fiz stubbing manual sempre que possível.
DPD
Esse é exatamente o tipo de coisa que eu gostaria de evitar: acho que os desenvolvedores de C ++ precisam ser especialmente pragmáticos sobre o teste. Você acabou testando, então acho que tudo bem.
Futlib
@ PDD: Pensei um pouco mais sobre isso, e acho que você está certo, a questão é que tipo de troca eu quero fazer. Vale a pena refatorar todo o sistema gráfico para testar algumas entidades? Eu não conheço nenhum bug lá, então provavelmente: Não. Se começar a parecer buggy, vou escrever testes. Pena que eu não posso aceitar sua resposta, porque é um comentário :)
futlib

Respostas:

5

Bem, o teste de unidade é apenas uma parte. Os testes de integração ajudam você com o problema da sua equipe. Os testes de integração podem ser escritos para todos os tipos de aplicativos, também para aplicativos nativos e OpenGL. Você deve verificar "Growing Object Oriented Software Guided by Tests" de Steve Freemann e Nat Pryce (por exemplo, http://www.amazon.com/Growing-Object-Oriented-Software-Guided-Signature/dp/0321503627 ). Ele o leva passo a passo pelo desenvolvimento de um aplicativo com GUI e comunicação de rede.

Teste de software que não foi testado é outra história. Verifique Michael Feathers "Trabalhando efetivamente com o código herdado" (http://www.amazon.com/Working-Effectively-Legacy-Michael-Feathers/dp/0131177052).

EricSchaefer
fonte
Eu conheço os dois livros. As coisas são: 1. Não queremos usar o TDD, porque não funcionou bem conosco. Nós queremos testes, mas não religiosamente. 2. Tenho certeza de que os testes de integração para aplicativos OpenGL são possíveis de alguma forma, mas é preciso muito esforço. Quero continuar melhorando o aplicativo, não iniciar um projeto de pesquisa.
futlib 19/07/12
Esteja ciente de que o teste de unidade "depois do fato" é sempre mais difícil do que o "teste primeiro", porque o código não foi projetado para ser testável (reutilizável, sustentável etc.). Se você quiser fazer isso de qualquer maneira, tente seguir os truques de Michael Feathers (por exemplo, costuras), mesmo se você evitar o TDD. Por exemplo, se você deseja estender uma função, tente algo como "método sprout" e tente manter o novo método testável. É possível fazer, mas IMHO mais difícil.
EricSchaefer
Concordo que o código não TDD não foi projetado para ser testável, mas não diria que não é sustentável ou reutilizável por si só - como comentei acima, algumas coisas simplesmente não precisam de interfaces e múltiplas implementações. Não é um problema no Mockito, mas em C ++, preciso tornar todas as funções que quero stub / mock virtuais. De qualquer forma, o código não testável é o meu maior problema no momento: tenho que mudar algumas coisas fundamentais para tornar algumas partes testáveis ​​e, portanto, quero uma boa lógica sobre o que testar, para garantir que valha a pena.
Futlib
Você está certo, é claro, terei o cuidado de tornar testável qualquer novo código que escrevo. Mas não será fácil, com a maneira como as coisas funcionam nessa base de código no momento.
Futlib
Ao adicionar um recurso / função, pense em como você poderia testá-lo. Você poderia injetar alguma dependência feia? Como você saberia que a função faz o que deve fazer? Você pode observar algum comportamento? Existe algum resultado que você possa verificar se está correto? Existem invariantes que você pode verificar?
22812 EricSchaefer
2

É uma pena que o TDD "não tenha funcionado bem para você". Eu acho que é a chave para entender para onde ir. Revise e entenda como o TDD não funcionou, o que você poderia ter feito melhor, por que houve dificuldade.

Portanto, é claro que seus testes de unidade não detectaram os bugs que você encontrou. Esse é o ponto. :-) Você não encontrou esses bugs porque os impediu de acontecer em primeiro lugar, pensando em como as interfaces deveriam funcionar e como garantir que elas fossem testadas corretamente.

Para responder, você questiona, como concluiu, que o código de teste de unidade que não foi projetado para ser testado é difícil. Para o código existente, pode ser mais eficaz usar um ambiente de teste funcional ou de integração do que um ambiente de teste de unidade. Teste o sistema em geral, concentrando-se em áreas específicas.

É claro que o novo desenvolvimento se beneficiará do TDD. À medida que novos recursos são adicionados, a refatoração para TDD pode ajudar a testar o novo desenvolvimento, além de permitir o desenvolvimento de um novo teste de unidade para as funções herdadas.

Bill Door
fonte
4
Fizemos TDD por cerca de um ano e meio, todos muito apaixonados por isso. No entanto, comparando os projetos TDD com os anteriores feitos sem TDD (mas não sem testes), eu não diria que eles são realmente mais estáveis ​​ou têm código melhor projetado. Talvez seja a nossa equipe: emparelhamos e analisamos muito, nossa qualidade de código sempre foi muito boa.
futlib
1
Quanto mais penso nisso, mais acho que o TDD simplesmente não se encaixa muito bem na tecnologia desse projeto em particular: Flex / Swiz. Há muitos eventos, ligações e injeção acontecendo que tornam as interações entre objetos complicadas e quase impossíveis de realizar testes unitários. A dissociação desses objetos não o torna melhor, porque eles funcionam corretamente em primeiro lugar.
Futlib
2

Eu não fiz TDD em C ++, então não posso comentar sobre isso, mas você deve testar o comportamento esperado do seu código. Embora a implementação possa mudar, o comportamento deve (normalmente?) Permanecer o mesmo. No mundo centralizado em Java \ C #, isso significaria que você só testaria os métodos públicos, escrevendo testes para o comportamento esperado e fazendo isso antes da implementação (o que geralmente é melhor dizer do que fazer :)).

Dante
fonte