O que é entendido em "unidade" no teste de unidade

9

Como eu entendo na teoria em "unidade", as pessoas querem dizer método (em POO). Porém, na prática, testes que verificam algum método isoladamente são testes de comportamento muito frágeis (verificando não o resultado, mas o fato de que algum método de dependência foi chamado). Então, eu vejo muitas pessoas que, por unidade, entendem um pequeno conjunto de classes estreitamente relacionadas. Nesse caso, apenas dependências externas são simuladas / stub e, para dependências que estão dentro da unidade, implementações reais são usadas. Nesse caso, há mais testes de estado, significativos (de acordo com a especificação) e não tão frágeis. Portanto, a pergunta é como você se sente sobre essas abordagens e é válido chamar o teste de unidade da segunda abordagem ou pode ser algum tipo de teste de integração de baixo nível?

Se você vir algumas considerações específicas sobre a aplicação do TDD por uma dessas maneiras de testar, agradeceria seus pensamentos.

SiberianGuy
fonte

Respostas:

11

Originalmente, o TDD veio do movimento ágil, onde os testes foram escritos com antecedência, como uma maneira de garantir que o que você codificou permanecesse correto, dada a especificação que agora estava bem definida no código de teste. Também apareceu como um aspecto muito importante da refatoração, pois quando você modificava seu código, podia confiar nos testes para provar que não havia alterado o comportamento do código.

Em seguida, as pessoas das ferramentas apareceram e pensaram que sabiam informações sobre seu código e, em seguida, poderiam gerar stubs de teste para ajudá-lo a escrever seus testes de unidade, e acho que foi aí que tudo deu errado.

Os stubs de teste são gerados por um computador que não tem idéia do que você está fazendo, apenas produz um stub para todos os métodos, sem pensar, porque é isso que ele deve fazer. Isso significa que você tem um caso de teste para cada método, independentemente da complexidade desse método ou se é adequado para teste isolado.

Isso está chegando ao teste do lado errado da metodologia TDD. No TDD, você deve descobrir o que o código deve fazer e produzir um código que atinja isso. Isso é gratificante, pois você acaba escrevendo testes que provam que o código faz o que o código faz, e não o que deveria fazer. Combinado com a geração automática de stubs de teste baseados em métodos, você desperdiça seu tempo provando cada pequeno aspecto do seu código que pode facilmente provar estar errado quando todos os pequenos pedaços são reunidos.

Quando Fowler descreveu o teste em seu livro, ele se referiu a testar cada classe com seu próprio método principal. Ele melhorou isso, mas o conceito ainda é o mesmo - você testa toda a classe para que ela funcione como um todo; todos os seus testes são agrupados para provar a interação de todos esses métodos, para que a classe possa ser reutilizada com expectativas definidas.

Acho que os kits de ferramentas de teste nos fizeram um desserviço, nos levaram a pensar que o kit de ferramentas é a única maneira de fazer as coisas quando, na verdade, você precisa pensar mais para obter o melhor resultado do seu código. Colocar cegamente o código de teste nos stubs de teste para pequenos pedaços significa apenas que você deve repetir seu trabalho em um teste de integração de qualquer maneira (e se você quiser fazer isso, por que não pular completamente a etapa agora redundante de teste de unidade)? Isso também significa que as pessoas perdem muito tempo tentando obter 100% de cobertura de teste e muito tempo criando grandes quantidades de códigos e dados simulados que seriam mais bem gastos, facilitando o código para o teste de integração (ou seja, se você tiver muito dependências de dados, o teste de unidade pode não ser a melhor opção)

Por fim, a fragilidade dos testes de unidade baseados em métodos apenas mostra o problema. A refatoração foi projetada para ser usada com testes de unidade. Se seus testes são interrompidos o tempo todo porque você está refatorando, algo deu errado com toda a abordagem. A refatoração gosta de criar e excluir métodos, portanto, obviamente, a abordagem de teste cega por método não é o que foi originalmente planejado.

Não tenho dúvidas de que muitos métodos terão testes escritos para eles, todos os métodos públicos de uma classe devem ser testados, mas você não pode se afastar do conceito de testá-los juntos como parte de um único caso de teste. Por exemplo, se eu tiver um método set e get, posso escrever testes que colocam os dados e verificar se os membros internos estão definidos ok, ou posso usar cada um para colocar alguns dados e retirá-los novamente para ver se ainda o mesmo e não ilegível. Isso está testando a classe, não cada método isoladamente. Se o setter depende de um método particular auxiliar, tudo bem - você não precisa zombar do método privado para garantir que o setter esteja funcionando, não se você testar a classe inteira.

Eu acho que a religião está entrando nesse tópico, portanto, você vê o cisma no que é agora conhecido como desenvolvimento 'orientado pelo comportamento' e 'orientado a testes' - o conceito original de teste de unidade era para desenvolvimento orientado a comportamentos.

gbjbaanb
fonte
10

Uma unidade é mais comumente definida como " a menor parte testável de um aplicativo ". Na maioria das vezes, sim, isso significa um método. E sim, isso significa que você não deve testar o resultado de um método dependente, mas apenas que o método é chamado (e somente uma vez, se possível, não em todos os testes desse método).

Você chama isso de frágil. Eu acho que isso está incorreto. Testes frágeis são aqueles que quebram sob a menor alteração em código não relacionado. Ou seja, aqueles que dependem de código irrelevante para a funcionalidade que está sendo testada.

No entanto, o que acho que você realmente quer dizer é que testar um método sem nenhuma de suas dependências não é completo. Nesse ponto, eu concordo. Você também precisa de testes de integração para garantir que as unidades de código estejam conectadas corretamente para criar um aplicativo.

Esse é exatamente o problema que o desenvolvimento orientado pelo comportamento e , especificamente , o orientado pelo teste de aceitação , resolve resolver. Mas isso não elimina a necessidade de testes de unidade / desenvolvimento orientado a testes; apenas o complementa.

pdr
fonte
Eu realmente queria dizer que os testes de comportamento são frágeis. Eles geralmente se tornam falsos negativos durante a alteração da base de código. Isso acontece menos com testes estaduais (mas testes estaduais são muito raramente para testes de unidade)
SiberianGuy
@ Idsa: Estou um pouco perdido por suas definições. Testes de comportamento são testes de integração, testando uma parte do comportamento conforme especificado. Lendo sua pergunta original, parece que quando você diz testes de estado, você quer dizer a mesma coisa.
PDR
por estado, quero dizer teste que verifica o estado, resultado de alguma função; por behavour I teste de média que não verifica o resultado, mas o fato de que alguma função foi chamado
SiberianGuy
@ Idsa: Nesse caso, discordo completamente. O que você está chamando de teste de estado, eu chamo de integração. O que você está chamando de comportamento, eu chamo de unidade. Os testes de integração, por sua própria definição, são mais frágeis. Google "unidade de teste de integração frágil" e você verá que não estou sozinha.
quer
há um registro de artigos sobre testes, mas quais deles compartilham sua opinião?
SiberianGuy
2

Como o nome sugere, você está testando um sujeito atômico em cada teste. Esse assunto geralmente é um método único. Vários testes podem testar o mesmo método, para cobrir o caminho feliz, possíveis erros etc. Você está testando o comportamento, não a mecânica interna. Portanto, o teste de unidade é realmente sobre testar a interface pública de uma classe, ou seja, um método específico.

No teste de unidade, um método precisa ser testado isoladamente, ou seja, stubbing / mocking / finging quaisquer dependências. Caso contrário, testar uma unidade com dependências 'reais' torna-a um teste de integração. Há tempo e local para os dois tipos de testes. Os testes de unidade garantem que um único assunto funcione conforme o esperado, independente. Os testes de integração garantem que assuntos "reais" trabalhem juntos corretamente.

Grant Palin
fonte
11
não exatamente, uma unidade é um assunto isolado, apenas porque o ferramental automatizado prefere tratar um método, pois isso não o faz, nem o melhor. 'Isolado' é a chave aqui. Mesmo se você testar métodos, também deverá testar os privados.
precisa
1

Minha regra de ouro: a menor unidade de código que ainda é complexa o suficiente para conter bugs.

Se este é um método, classe ou subsistema depende do código específico, nenhuma regra geral pode ser dada.

Por exemplo, ele não fornece nenhum valor para testar métodos simples getter / setter isoladamente ou métodos de invólucro que chamam apenas outro método. Mesmo uma classe inteira pode ser muito simples de testar, se a classe for apenas um invólucro ou adaptador fino. Se a única coisa a ser testada é se um método em uma simulação é chamado, o código em teste é muito fino.

Em outros casos, um único método pode executar algum cálculo complexo que é valioso para testar isoladamente.

Em muitos casos, as partes complexas não são classes individuais, mas a integração entre classes. Então você testa duas ou mais aulas ao mesmo tempo. Alguns dirão que isso não é testes de unidade, mas testes de integração, mas não importa a terminologia: você deve testar onde está a complexidade e esses testes devem fazer parte do conjunto de testes.

JacquesB
fonte