ti-enxame.com

O que você deve testar com testes de unidade?

Acabei de terminar a faculdade e estou começando a universidade em algum lugar na próxima semana. Vimos testes de unidade, mas meio que não os usamos muito; e todo mundo fala sobre eles, então achei que talvez devesse fazer um pouco.

O problema é que eu não sei o que para testar. Devo testar o caso comum? O caso Edge? Como sei que uma função está adequadamente coberta?

Eu sempre tenho a terrível sensação de que, embora um teste prove que uma função funciona para um determinado caso, é totalmente inútil provar que a função funciona, ponto final.

128
zneak

Minha filosofia pessoal foi assim:

  1. Teste o caso comum de tudo o que puder. Isso informará quando esse código será quebrado depois que você fizer alguma alteração (que é, na minha opinião, o maior benefício dos testes de unidade automatizados).
  2. Teste os casos do Edge de alguns códigos incomumente complexos que você acha que provavelmente terão erros.
  3. Sempre que encontrar um erro, escreva um caso de teste para cobri-lo antes de corrigi-lo
  4. Adicione testes de casos do Edge a códigos menos críticos sempre que alguém tiver tempo para matar.
124
Fishtoaster

Entre a infinidade de respostas até agora, ninguém abordou particionamento de equivalência e análise de valor-limite , considerações vitais na resposta à pergunta em questão. Todas as outras respostas, embora úteis, são qualitativas, mas é possível - e preferível - ser quantitativa aqui. O @fishtoaster fornece algumas diretrizes concretas, apenas espiando sob a cobertura da quantificação de testes, mas o particionamento de equivalência e a análise de valor de limite nos permitem fazer melhor.

No particionamento de equivalência , você divide o conjunto de todas as entradas possíveis em grupos com base nos resultados esperados. Qualquer entrada de um grupo produzirá resultados equivalentes, portanto, esses grupos serão chamados classes de equivalência. (Observe que resultados equivalentes não significam resultados idênticos.)

Como um exemplo simples, considere um programa que deve transformar letras minúsculas ASCII em caracteres maiúsculos. Outros caracteres devem passar por uma transformação de identidade, ou seja, permanecer inalterados. Aqui está uma possível divisão em classes de equivalência:

| # |  Equivalence class    | Input        | Output       | # test cases |
+------------------------------------------------------------------------+
| 1 | Lowercase letter      | a - z        | A - Z        | 26           |
| 2 | Uppercase letter      | A - Z        | A - Z        | 26           |
| 3 | Non-alphabetic chars  | [email protected]#,/"... | [email protected]#,/"... | 42           |
| 4 | Non-printable chars   | ^C,^S,TAB... | ^C,^S,TAB... | 34           |

A última coluna relata o número de casos de teste, se você enumerar todos eles. Tecnicamente, pela regra 1 do @ fishtoaster, você incluiria 52 casos de teste - todos os das duas primeiras linhas dadas acima se enquadram no "caso comum". A regra 2 da @ fishtoaster adicionaria algumas ou todas as linhas 3 e 4 acima também. Mas com o particionamento de equivalência testando qualquer um caso de teste em cada classe de equivalência é suficiente. Se você escolher "a" ou "g" ou "w", estará testando o mesmo caminho de código. Assim, você tem um total de 4 casos de teste em vez de 52 ou mais.

A análise do valor limite recomenda um leve refinamento: essencialmente, sugere que nem todo membro de uma classe de equivalência é, bem, equivalente. Ou seja, os valores nas fronteiras também devem ser considerados dignos de um caso de teste por si mesmos. (Uma justificativa fácil para isso é o infame erro de uma a uma !) Portanto, para cada classe de equivalência, você pode ter 3 entradas de teste. Observando o domínio de entrada acima - e com algum conhecimento de ASCII -), eu poderia criar essas entradas de caso de teste:

| # | Input                | # test cases |
| 1 | a, w, z              | 3            |
| 2 | A, E, Z              | 3            |
| 3 | 0, 5, 9, !, @, *, ~  | 7            |
| 4 | nul, esc, space, del | 4            |

(Assim que você obtém mais de 3 valores de limite, o que sugere que você pode querer repensar suas delineações de classe de equivalência originais, mas isso foi simples o suficiente para que eu não voltasse a revisá-las.) Assim, a análise de valor de limite nos leva a apenas 17 casos de teste - com uma alta confiança de cobertura completa - em comparação com 128 casos de teste para realizar testes exaustivos. (Sem mencionar que a combinatória determina que testes exaustivos são simplesmente inviáveis ​​para qualquer aplicação do mundo real!)

68
Michael Sorens

Provavelmente minha opinião não é muito popular. Mas sugiro que você seja econômico com testes de unidade. Se você tiver muitos testes de unidade, poderá acabar gastando metade do seu tempo ou mais com a manutenção de testes, em vez da codificação real.

Eu sugiro que você escreva testes para coisas que você tem um mau pressentimento ou coisas muito cruciais e/ou elementares. Os testes de unidade IMHO não substituem uma boa engenharia e codificação defensiva. Atualmente, trabalho em um projeto que é mais ou menos inutilizável. É realmente estável, mas é difícil refatorar. De fato, ninguém tocou esse código em um ano e a pilha de software em que ele se baseia tem 4 anos. Por quê? Porque está cheio de testes de unidade, para ser mais preciso: testes de unidade e testes de integração automatizados. (Já ouviu falar de pepino e coisas do gênero?) E aqui está a melhor parte: Este (ainda) software inutilizável foi desenvolvido por uma empresa cujos funcionários são pioneiros no cenário de desenvolvimento orientado a testes. : D

Então, minha sugestão é:

  • Comece a escrever testes depois você desenvolveu o esqueleto básico; caso contrário, a refatoração pode ser dolorosa. Como desenvolvedor que desenvolve para outras pessoas, você nunca obtém os requisitos corretamente desde o início.

  • Verifique se os testes de unidade podem ser realizados rapidamente. Se você tiver testes de integração (como pepino), tudo bem se eles demorarem um pouco mais. Mas testes de longa duração não são divertidos, acredite. (As pessoas esquecem todos os motivos pelos quais o C++ se tornou menos popular ...)

  • Deixe esse material de TDD para os especialistas em TDD.

  • E sim, às vezes você se concentra nos casos do Edge, às vezes nos casos comuns, dependendo de onde espera o inesperado. Embora se você sempre espera o inesperado, deve realmente repensar o fluxo de trabalho e a disciplina. ;-)

20
Philip

Se você estiver testando primeiro com o Test Driven Development, sua cobertura aumentará em 90% ou mais, porque você não adicionará funcionalidade sem primeiro escrever um teste de unidade com falha.

Se você estiver adicionando testes após o fato, não posso recomendar o suficiente para que você obtenha uma cópia de Trabalhando efetivamente com o código legado de Michael Feathers e dê uma olhada em algumas das técnicas para adicionar testes ao seu código e maneiras de refatorá-lo para torná-lo mais testável.

8
Paddyslacker

Se você começar a seguir as práticas Test Driven Development , elas classificarão o guiarão ao longo do processo e saberá o que testar virá naturalmente. Alguns lugares para começar:

Os testes são os primeiros

Nunca, nunca escreva código antes de escrever os testes. Consulte Repetição de refator verde-vermelho para obter uma explicação.

Escreva testes de regressão

Sempre que encontrar um erro, escreva uma caixa de teste e verifique se falha . A menos que você possa reproduzir um bug através de uma caixa de teste com falha, você realmente não o encontrou.

Repetição de refator verde-vermelho

Vermelho : comece escrevendo um teste mais básico para o comportamento que você está tentando implementar. Pense nesta etapa como escrevendo algum código de exemplo que usa a classe ou função em que você está trabalhando. Verifique se ele compila/não possui erros de sintaxe e se falha . Isso deve ser óbvio: você não escreveu nenhum código, portanto deve falhar, certo? O importante a aprender aqui é que, a menos que você veja o teste falhar pelo menos uma vez, nunca poderá ter certeza de que, se ele for aprovado, ele será feito por causa de algo que você fez por algum motivo falso.

Verde : Escreva o código mais simples e estúpido que realmente faz o teste passar. Não tente ser esperto. Mesmo que você veja que há um caso óbvio do Edge, mas o teste leva em consideração, não escreve código para lidar com isso (mas não se esqueça do caso do Edge: você ' Preciso disso mais tarde). A idéia é que todo código que você escreve, todo if, todo try: ... except: ... deve ser justificado por um caso de teste. O código não precisa ser elegante, rápido ou otimizado. Você só quer que o teste seja aprovado.

Refatorar : limpe seu código, acerte os nomes dos métodos. Veja se o teste ainda está passando. Otimizar. Execute o teste novamente.

Repita : Você se lembra do caso Edge que o teste não cobriu, certo? Então, agora é o seu grande momento. Escreva uma caixa de teste que cubra essa situação, observe-a falhar, escreva algum código, veja-o passar, refatorar.

Teste seu código

Você está trabalhando em algum trecho de código específico, e é exatamente isso que deseja testar. Isso significa que você não deve testar as funções da biblioteca, a biblioteca padrão ou o seu compilador. Além disso, tente evitar testar o "mundo". Isso inclui: chamar APIs da Web externas, algumas coisas intensivas em bancos de dados, etc. Sempre que você tentar zombar (crie um objeto que siga a mesma interface, mas retorne dados estáticos e predefinidos).

6
Ryszard Szopa

Para testes de unidade, comece com o teste de que ele faz o que foi projetado para fazer. Esse deve ser o primeiro caso que você escreve. Se parte do design é "ele deve gerar uma exceção se você passar por lixo", teste-o também, pois isso faz parte do design.

Comece com isso. À medida que você obtém experiência na execução dos testes mais básicos, começará a aprender se isso é suficiente ou não e começará a ver outros aspectos do seu código que precisam de testes.

3
Bryan Oakley

A resposta das ações é "teste tudo o que poderia quebrar" .

O que é simples demais para quebrar? Campos de dados, acessadores de propriedades com morte encefálica e despesas gerais semelhantes. Qualquer outra coisa provavelmente implementa alguma parte identificável de um requisito e pode se beneficiar de ser testado.

Obviamente, sua milhagem - e as práticas do seu ambiente de trabalho - podem variar.

0
Jeffrey Hantin