O que você deve testar com testes de unidade?

122

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 pensei que talvez devesse fazer um pouco.

O problema é que não sei o que testar. Devo testar o caso comum? O caso de ponta? 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.

zneak
fonte
Dê uma olhada no Blog de Roy Osherove . Existem muitas informações sobre os testes de unidade, incluindo vídeos. Ele também escreveu um livro, "The art of Unit Testing", que é muito bom.
Piers Myers
9
Eu me pergunto o que você acha disso depois de quase 5 anos? Porque cada vez mais sinto que as pessoas deveriam saber melhor "o que não fazer testes em unidade" hoje em dia. O desenvolvimento orientado ao comportamento evoluiu das perguntas que você fez.
Remigijus Pankevičius

Respostas:

121

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 extremos 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 extremos a códigos menos críticos sempre que alguém tiver tempo para matar.
Fishtoaster
fonte
1
Obrigado por isso, eu estava me debatendo aqui com as mesmas perguntas do OP.
Stephen
5
+1, embora eu também testasse os casos extremos de quaisquer funções do tipo biblioteca / utilitário para garantir que você tenha uma API lógica. por exemplo, o que acontece quando um nulo é passado? e quanto à entrada vazia? Isso ajudará a garantir que seu design seja lógico e documentará o comportamento da caixa de canto.
Mikera #
7
# 3 parece uma resposta muito sólida, pois é um exemplo da vida real de como um teste de unidade poderia ter ajudado. Se ele quebrou uma vez, pode quebrar novamente.
Ryan Griffith
Depois de começar, acho que não sou muito criativo com a criação de testes. Então, eu os uso como o nº 3 acima, o que garante a tranqüilidade de que esses bugs nunca mais serão detectados.
precisa saber é o seguinte
Sua resposta foi apresentada neste artigo popular de mídia: hackernoon.com/…
BugHunterUK 7/17
67

Entre a infinidade de respostas até agora, ninguém abordou o particionamento de equivalência e a 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 são chamados de classes de equivalência . (Observe que resultados equivalentes não significam resultados idênticos.)

Como um exemplo simples, considere um programa que deve transformar caracteres ASCII minúsculos 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  | 0-9!@#,/"... | 0-9!@#,/"... | 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 equivalência de particionamento testar 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 ligeiro 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 um por um !) Portanto, para cada classe de equivalência, você pode ter 3 entradas de teste. Olhando para o domínio de entrada acima - e com algum conhecimento dos valores ASCII -, posso apresentar estas 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ê obtiver mais de três 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!)

Michael Sorens
fonte
3
+1 É exatamente assim que escrevo intuitivamente meus testes. Agora eu posso colocar um nome nele :) Obrigado por compartilhar isso.
precisa
+1 para "respostas qualitativas são úteis, mas é possível - e preferível - ser quantitativo"
Jimmy Breck-McKye 4/15/15
Penso que esta é uma boa resposta se a directiva for "como posso obter uma boa cobertura com os meus testes". Eu acho que seria útil encontrar uma abordagem pragmática em cima disso - é o objetivo que todos os ramos de cada pedaço de lógica em cada camada sejam testados exaustivamente dessa maneira?
Kieren Johnstone
18

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 tipo?) 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 de desenvolver o esqueleto básico; caso contrário, a refatoração poderá ser dolorosa. Como desenvolvedor que desenvolve para outras pessoas, você nunca obtém os requisitos corretamente desde o início.

  • Verifique se seus 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 extremos, à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. ;-)

Philip
fonte
2
Você pode dar mais detalhes sobre o motivo pelo qual os testes tornam esse software difícil de refatorar?
Mike Partridge
6
Big +1. Com paredes de testes de unidade que testam a implementação em vez das regras faz com que qualquer mudança requer 2-3x como muitos
TheLQ
9
Como o código de produção mal escrito, os testes de unidade mal escritos são difíceis de manter. "Muitos testes de unidade" soa como uma falha em permanecer seco; cada teste deve abordar / provar uma parte específica do sistema.
Allan
1
Cada teste de unidade deve verificar uma coisa, para que não haja muitos testes de unidade, mas faltando testes. Se seus testes de unidade são complexos, isso é outro problema.
graffic
1
-1: acho que este post está mal escrito. Múltiplas coisas mencionadas e não sei como elas se relacionam. Se o ponto da resposta for "seja econômico", como o seu exemplo se relaciona? Parece que a sua situação de exemplo (embora real) tem testes de unidade ruins. Por favor, explique quais lições devo aprender com isso e como isso me ajuda a ser econômico. Além disso, sinceramente, simplesmente não sei o que você quer dizer quando diz Leave this TDD stuff to the TDD-experts.
Alexander Bird
8

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 do Working Effective With Legacy Code de Michael Feathers e dê uma olhada em algumas das técnicas para adicionar testes ao seu código e maneiras de refatorar seu código para torná-lo mais testável.

Paddyslacker
fonte
Como você calcula esse percentual de cobertura? O que significa cobrir 90% do seu código?
zneak
2
@ zneak: existem ferramentas de cobertura de código que as calcularão para você. Um google rápido para "cobertura de código" deve exibir vários deles. A ferramenta rastreia as linhas de código que são executadas durante a execução dos testes e as bases que representam o total de linhas de código na (s) montagem (ões) para gerar a porcentagem de cobertura.
Steven Evers
-1. Não responde à pergunta: #The problem is, I don't know _what_ to test
Alexander Bird
6

Se você começar a seguir test driven development práticas, eles tipo irá guiá- lo através do processo e saber o que testar virá naturalmente. Alguns lugares para começar:

Os testes vêm primeiro

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

Escrever testes de regressão

Sempre que encontrar um bug, escreva um testcase e verifique se ele 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 vermelho-verde-refator

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 for aprovado, o fará 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 de borda óbvio, mas o teste leva em consideração, não escreva código para lidar com isso (mas não se esqueça do caso de borda: você precisará dele mais tarde). A idéia é que todo código que você escreve, todo if, todo try: ... except: ...seja 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.

Refatorador : 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 extremo 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).

Ryszard Szopa
fonte
1
Supondo que eu já tenha uma base de código existente e (até onde eu possa ver) funcionando, o que devo fazer?
zneak
Isso pode ser um pouco mais difícil (dependendo de como o código é escrito). Comece com testes de regressão (eles sempre fazem sentido) e tente escrever testes de unidade para provar a si mesmo que entende o que o código está fazendo. É fácil ficar impressionado com a quantidade de trabalho que parece (aparentemente) fazer, mas: alguns testes são sempre melhores do que nenhum teste.
Ryszard Szopa
3
-1 Não acho que seja uma resposta muito boa para esta pergunta . A questão não é sobre TDD, é perguntar sobre o que testar ao escrever testes de unidade. Eu acho que uma boa resposta para a pergunta real deve se aplicar a uma metodologia não TDD.
Bryan Oakley
1
Se você tocá-lo, teste-o. E o Código Limpo (Robert C Martin) sugere que você escreva "testes de aprendizado" para códigos de terceiros. Dessa forma, você aprende a usá-lo e realiza testes caso uma nova versão altere o comportamento que você está usando.
Roger Willcocks
3

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 para o 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, você começa a aprender se isso é suficiente ou não e começa a ver outros aspectos do seu código que precisam de testes.

Bryan Oakley
fonte
0

A resposta das ações é "testar 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.

Jeffrey Hantin
fonte
OK. Então, quais casos devo testar? O caso "normal"? O caso de ponta?
zneak 25/09/10
3
Regra prática? Um ou dois no meio do caminho dourado, apenas dentro e fora de qualquer borda.
Jeffrey Hantin
@JeffreyHantin Essa é a "análise de valor limite" em uma resposta diferente.
Roger Willcocks