Estou tentando tornar meu código mais robusto e tenho lido sobre testes de unidade, mas acho muito difícil encontrar um uso útil real. Por exemplo, o exemplo da Wikipedia :
public class TestAdder {
public void testSum() {
Adder adder = new AdderImpl();
assert(adder.add(1, 1) == 2);
assert(adder.add(1, 2) == 3);
assert(adder.add(2, 2) == 4);
assert(adder.add(0, 0) == 0);
assert(adder.add(-1, -2) == -3);
assert(adder.add(-1, 1) == 0);
assert(adder.add(1234, 988) == 2222);
}
}
Eu sinto que esse teste é totalmente inútil, porque você precisa calcular manualmente o resultado desejado e testá-lo, sinto que um teste de unidade melhor aqui seria
assert(adder.add(a, b) == (a+b));
mas isso está apenas codificando a própria função no teste. Alguém pode me fornecer um exemplo em que o teste de unidade é realmente útil? Para sua informação, atualmente estou codificando principalmente funções "procedurais" que recebem ~ 10 booleanos e algumas ints e me dão um resultado int com base nisso. Sinto que o único teste de unidade que eu poderia fazer seria simplesmente re-codificar o algoritmo no teste. edit: eu também deveria ter precisado isso enquanto portava (possivelmente mal projetado) código ruby (que eu não fiz)
fonte
How does unit testing work?
Ninguém realmente sabe :) #Respostas:
Os testes de unidade, se você estiver testando unidades pequenas o suficiente, sempre estão afirmando o óbvio.
A razão pela qual
add(x, y)
até é mencionada uma prova unitária é que, algum tempo depois, alguém entraráadd
e colocará um código especial de manipulação de lógica tributária, sem perceber que a adição é usada em qualquer lugar.Os testes de unidade são muito sobre o princípio associativo: se A faz B e B faz C, então A faz C. "A faz C" é um teste de nível superior. Por exemplo, considere o seguinte código comercial completamente legítimo:
À primeira vista, isso parece um ótimo método para teste de unidade, porque tem um objetivo muito claro. No entanto, ele faz cerca de 5 coisas diferentes. Cada coisa tem um caso válido e inválido e fará uma enorme permutação de testes de unidade. Idealmente, isso é dividido ainda mais:
Agora estamos reduzidos a unidades. Um teste de unidade não se importa com o que
_userRepo
considera um comportamento válidoFetchValidUser
, apenas com o nome. Você pode usar outro teste para garantir exatamente o que um usuário válido constitui. Da mesma forma paraCheckUserForRole
... você separou seu teste de saber como é a estrutura da Função. Você também desvinculou todo o seu programa de ser vinculado estritamenteSession
. Eu imagino que todas as peças que faltam aqui ficariam assim:Ao refatorar, você realizou várias coisas ao mesmo tempo. O programa é muito mais favorável ao arrancar estruturas subjacentes (você pode abandonar a camada do banco de dados no NoSQL) ou adicionar bloqueios sem interrupções quando perceber que
Session
não é seguro para threads ou o que quer. Você também já fez testes bastante diretos para escrever para essas três dependências.Espero que isto ajude :)
fonte
Tenho certeza de que cada uma de suas funções processuais é determinística, portanto, ele retorna um resultado int específico para cada conjunto de valores de entrada. Idealmente, você teria uma especificação funcional a partir da qual poderá descobrir qual resultado deve receber para determinados conjuntos de valores de entrada. Na falta disso, você pode executar o código ruby (que supostamente funciona corretamente) para determinados conjuntos de valores de entrada e registrar os resultados. Em seguida, você precisa HARD CODE os resultados em seu teste. O teste deve ser uma prova de que seu código realmente produz resultados que são conhecidos por estarem corretos .
fonte
Como ninguém mais parece ter fornecido um exemplo real:
Você diz:
Mas o ponto é que é muito menos provável que você cometa um erro (ou pelo menos mais provável que note seus erros) ao fazer os cálculos manuais e depois ao codificar.
Qual a probabilidade de você cometer um erro no código de conversão decimal em romano? Provavelmente. Qual é a probabilidade de você cometer um erro ao converter números decimais em romanos manualmente? Não muito provável. É por isso que testamos contra cálculos manuais.
Qual a probabilidade de você cometer um erro ao implementar uma função de raiz quadrada? Provavelmente. Qual a probabilidade de você cometer um erro ao calcular manualmente uma raiz quadrada? Provavelmente mais provável. Mas com o sqrt, você pode usar uma calculadora para obter as respostas.
Então, eu vou especular sobre o que está acontecendo aqui. Suas funções são meio complicadas, por isso é difícil descobrir pelas entradas qual deve ser a saída. Para fazer isso, você deve executar manualmente (em sua cabeça) a função para descobrir qual é a saída. Compreensivelmente, isso parece meio inútil e propenso a erros.
A chave é que você deseja encontrar as saídas corretas. Mas você precisa testar essas saídas em relação a algo conhecido como correto. Não é bom escrever seu próprio algoritmo para calcular isso, porque isso pode muito bem estar incorreto. Nesse caso, é muito difícil calcular manualmente os valores.
Eu voltaria ao código ruby e executaria essas funções originais com vários parâmetros. Eu pegava os resultados do código ruby e os colocava no teste de unidade. Dessa forma, você não precisa fazer o cálculo manual. Mas você está testando contra o código original. Isso deve ajudar a manter os resultados iguais, mas se houver bugs no original, isso não ajudará. Basicamente, você pode tratar o código original como a calculadora no exemplo sqrt.
Se você mostrou o código real que está transportando, podemos fornecer um feedback mais detalhado sobre como abordar o problema.
fonte
Você está quase correto para uma classe tão simples.
Experimente para uma calculadora mais complexa. Como uma calculadora de pontuação de boliche.
O valor dos testes de unidade é mais facilmente visto quando você tem regras "comerciais" mais complexas com diferentes cenários para testar.
Não estou dizendo que você não deve testar uma calculadora da série (sua conta calcula problemas em valores como 1/3 que não podem ser representados? O que isso faz com a divisão por zero?), Mas você verá o valorize mais claramente se você testar algo com mais ramos para obter cobertura.
fonte
Apesar do fanatismo religioso cerca de 100% da cobertura do código, direi que nem todo método deve ser testado em unidade. Somente funcionalidade que contém lógica comercial significativa. Uma função que simplesmente adiciona número é inútil para testar.
Existe o seu problema real ali. Se o teste de unidade parecer estranhamente difícil ou inútil, é provável que seja devido a uma falha no projeto. Se fosse mais orientado a objetos, suas assinaturas de método não seriam tão grandes e haveria menos entradas possíveis para testar.
Eu não preciso entrar no meu OO é superior à programação processual ...
fonte
No meu ponto de vista, os testes de unidade são úteis até na sua classe de somadores pequenos: não pense em "recodificar" o algoritmo e pense nele como uma caixa preta com o único conhecimento que você tem sobre o comportamento funcional (se você estiver familiarizado com a multiplicação rápida, você conhece algumas tentativas mais rápidas, porém mais complexas, do que usar o "a * b") e sua interface pública. Do que você deve se perguntar "O que diabos pode dar errado?" ...
Na maioria dos casos, isso acontece na borda (vejo que você já teste esses padrões adicionando ++, -, + -, 00 - tempo para completá-los com - +, 0+, 0-, +0, -0). Pense no que acontece em MAX_INT e MIN_INT ao adicionar ou subtrair (adicionar negativos;)) lá. Ou tente garantir que seus testes tenham a aparência exata do que acontece ao redor de zero.
Em suma, o segredo é muito simples (talvez também para os mais complexos;)) para classes simples: pense nos contratos de sua classe (consulte o projeto por contrato) e depois teste contra eles. Quanto melhor você conhecer suas inv, pre e post, mais "completas" serão seus testes.
Dica para suas classes de teste: tente escrever apenas uma afirmação em um método. Dê bons nomes aos métodos (por exemplo, "testAddingToMaxInt", "testAddingTwoNegatives") para obter o melhor feedback quando o teste falhar após a alteração do código.
fonte
Em vez de testar um valor de retorno calculado manualmente ou duplicar a lógica no teste para calcular o valor de retorno esperado, teste o valor de retorno para uma propriedade esperada.
Por exemplo, se você deseja testar um método que inverte uma matriz, não deseja inverter manualmente seu valor de entrada, multiplique o valor de retorno pela entrada e verifique se obtém a matriz de identidade.
Para aplicar essa abordagem ao seu método, você precisará considerar sua finalidade e semântica, para identificar quais propriedades o valor de retorno terá em relação às entradas.
fonte
Os testes de unidade são uma ferramenta de produtividade. Você recebe uma solicitação de alteração, implementa-a e, em seguida, executa seu código no gambit de teste de unidade. Esse teste automatizado economiza tempo.
I feel that this test is totally useless, because you are required to manually compute the wanted result and test it, I feel like a better unit test here would be
Um ponto discutível. O teste no exemplo mostra apenas como instanciar uma classe e executá-la através de uma série de testes. Concentrar-se nas minúcias de uma única implementação está faltando a floresta para as árvores.
Can someone provide me with an example where unit testing is actually useful?
Você tem uma entidade de funcionário. A entidade contém um nome e endereço. O cliente decide adicionar um campo ReportsTo.
Esse é um teste básico do BL para trabalhar com um funcionário. O código passará / falhará na alteração do esquema que você acabou de fazer. Lembre-se de que afirmações não são a única coisa que o teste faz. A execução do código também garante que não haja exceções.
Com o tempo, a realização dos testes facilita as alterações em geral. O código é testado automaticamente em busca de exceções e em relação às asserções feitas. Isso evita grande parte das despesas gerais incorridas pelos testes manuais de um grupo de controle de qualidade. Embora a UI ainda seja bastante difícil de automatizar, as outras camadas geralmente são muito fáceis, desde que você use os modificadores de acesso corretamente.
I feel like the only unit testing I could do would be to simply re-code the algorithm in the test.
Até a lógica processual é facilmente encapsulada dentro de uma função. Encapsular, instanciar e passar o int / primitivo a ser testado (ou objeto de simulação). Não copie e cole o código em um teste de unidade. Isso derrota o DRY. Ele também derrota o teste inteiramente porque você não está testando o código, mas uma cópia do código. Se o código que deveria ter sido testado mudar, o teste ainda passará!
fonte
Tomando o seu exemplo (com um pouco de refatoração),
não ajuda a:
math.add
se comporta internamente,É como dizer:
math.add
pode conter centenas de LOC; veja abaixo).Isso também significa que você não precisa adicionar testes como:
Eles também não ajudam, ou pelo menos, uma vez que você fez a primeira afirmação, a segunda não traz nada útil.
Em vez disso, o que dizer de:
Isso é auto-explicativo e muito útil para você e para a pessoa que manterá o código-fonte posteriormente. Imagine que essa pessoa faça uma ligeira modificação no
math.add
para simplificar o código e otimizar o desempenho e veja o resultado do teste como:essa pessoa entenderá imediatamente que o método recém-modificado depende da ordem dos argumentos: se o primeiro argumento for um número inteiro e o segundo for um numérico longo, o resultado seria um inteiro, enquanto um numérico longo é esperado.
Da mesma forma, obter o valor real de
4.141592
na primeira asserção é auto-explicativo: você sabe que o método deve lidar com uma grande precisão , mas, na verdade, falha.Pela mesma razão, duas afirmações a seguir podem fazer sentido em alguns idiomas:
Além disso, o que dizer de:
Também é auto-explicativo: você deseja que seu método seja capaz de lidar corretamente com o infinito. Ir além do infinito ou lançar uma exceção não é um comportamento esperado.
Ou talvez, dependendo do seu idioma, isso faça mais sentido?
fonte
Para uma função muito simples como add, o teste pode ser considerado desnecessário, mas à medida que suas funções se tornam mais complexas, fica cada vez mais óbvio o motivo pelo qual o teste é necessário.
Pense no que você faz quando está programando (sem teste de unidade). Geralmente você escreve algum código, executa, vê que funciona e passa para a próxima coisa, certo? À medida que você escreve mais código, especialmente em um sistema / GUI / site muito grande, você descobre que precisa fazer mais e mais "correr e ver se funciona". Você tem que tentar isso e tentar aquilo. Depois, faça algumas alterações e tente novamente as mesmas coisas. Torna-se muito óbvio que você poderia economizar tempo escrevendo testes de unidade que automatizariam toda a parte "executando e ver se funciona".
À medida que seus projetos se tornam cada vez maiores, o número de coisas que você precisa "executar e ver se funciona" torna-se irreal. Então você acaba executando e tentando alguns dos principais componentes da GUI / projeto e depois esperando que tudo esteja bem. Isto é uma receita para o desastre. É claro que você, como ser humano, não pode testar repetidamente todas as situações possíveis que seus clientes possam usar se a GUI for usada por literalmente centenas de pessoas. Se você tivesse testes de unidade, poderia simplesmente executar o teste antes de enviar a versão estável ou mesmo antes de se comprometer com o repositório central (se o seu local de trabalho usar um). E, se houver algum erro encontrado posteriormente, você pode adicionar um teste de unidade para verificar no futuro.
fonte
Um dos benefícios de escrever testes de unidade é que ele ajuda a escrever código mais robusto, forçando-o a pensar em casos extremos. Que tal testar alguns casos extremos, como excesso de número inteiro, truncamento decimal ou manipulação de nulos para os parâmetros?
fonte
Talvez você assuma que add () foi implementado com a instrução ADD. Se algum programador júnior ou engenheiro de hardware reimplementar a função add () usando ANDS / ORS / XORS, bit inverte e alterna, convém testá-lo de acordo com a instrução ADD.
Em geral, se você substituir as tripas de add (), ou a unidade em teste, por um número aleatório ou gerador de saída, como você saberia que algo estava quebrado? Codifique esse conhecimento em seus testes de unidade. Se ninguém souber se está quebrado, basta verificar o código de rand () e voltar para casa, seu trabalho está feito.
fonte
Eu posso ter perdido isso entre todas as respostas, mas, para mim, a principal motivação por trás do Teste de Unidade é menos provar a correção de um método hoje, mas provar a correção contínua desse método sempre que você o altera .
Tome uma função simples, como retornar o número de itens em alguma coleção. Hoje, quando sua lista é baseada em uma estrutura de dados interna que você conhece bem, você pode pensar que esse método é tão dolorosamente óbvio que você não precisa de um teste para isso. Em alguns meses ou anos, você (ou outra pessoa ) decide [s] substituir a estrutura interna da lista. Você ainda precisa saber que getCount () retorna o valor correto.
É aí que seus testes de unidade realmente se destacam.
Você pode alterar a implementação interna do seu código, mas para qualquer consumidor desse código, o resultado permanece o mesmo.
fonte