Determinando o que é um teste de unidade útil


47

Eu estive pesquisando os documentos do phpunit e encontrei a seguinte citação:

Você sempre pode escrever mais testes. No entanto, você descobrirá rapidamente que apenas uma fração dos testes que você pode imaginar são realmente úteis. O que você deseja é escrever testes que falhem, mesmo que você ache que eles devam funcionar, ou testes que tenham sucesso, mesmo que você ache que eles devam falhar. Outra maneira de pensar é em termos de custo / benefício. Você deseja escrever testes que pagarão com informações. --Erich Gamma

Isso me fez pensar. Como você determina o que torna um teste de unidade mais útil que outro, além do que é mencionado nessa citação sobre custo / benefício. Como você decide para qual parte do seu código você cria testes de unidade? Estou perguntando isso, porque outra daquelas citações também dizia:

Então, se não se trata de testes, o que é isso? Trata-se de descobrir o que você está tentando fazer antes de fugir para tentar fazê-lo. Você escreve uma especificação que identifica um pequeno aspecto do comportamento de forma concisa, inequívoca e executável. É simples assim. Isso significa que você escreve testes? Não. Isso significa que você escreve especificações do que seu código terá que fazer. Isso significa que você especifica o comportamento do seu código com antecedência. Mas não muito à frente do tempo. De fato, pouco antes de você escrever o código, é melhor porque é quando você tem o máximo de informações disponíveis até o momento. Como o TDD bem feito, você trabalha em pequenos incrementos ... especificando um pequeno aspecto do comportamento de cada vez e implementando-o. Quando você percebe que se trata de especificar comportamento e não de escrever testes, seu ponto de vista muda. De repente, a idéia de ter uma classe de teste para cada uma de suas classes de produção é ridiculamente limitante. E o pensamento de testar cada um dos seus métodos com seu próprio método de teste (em um relacionamento de 1 a 1) será risível. --Dave Astels

A seção importante disso é

* E o pensamento de testar cada um de seus métodos com seu próprio método de teste (em um relacionamento de 1 a 1) será risível. *

Portanto, se a criação de um teste para cada método é 'risível', como / quando você escolhe para que escreve os testes?


5
É uma boa pergunta, mas acho que é uma questão independente da linguagem de programação - por que você o marcou php?

Aqui estão alguns artigos realmente bons que me forneceram uma boa orientação sobre quais testes de unidade eu deveria escrever e quais áreas são importantes para fornecer testes automatizados. - blog.stevensanderson.com/2009/11/04/... - blog.stevensanderson.com/2009/08/24/... - ayende.com/blog/4218/scenario-driven-tests
Kane

Respostas:


27

Quantos testes por método?

Bem, o máximo teórico e altamente impraticável é a complexidade do N-Path (suponha que todos os testes abranjam maneiras diferentes através do código;)). O mínimo é UM !. Por método público , isto é, ele não testa detalhes da implementação, apenas comportamentos externos de uma classe (retornam valores e chamam outros objetos).

Você cita:

* E o pensamento de testar cada um de seus métodos com seu próprio método de teste (em um relacionamento de 1 a 1) será risível. *

e depois pergunte:

Portanto, se a criação de um teste para cada método é 'risível', como / quando você escolhe para que escreve os testes?

Mas acho que você não entendeu o autor aqui:

A idéia de ter one test methodpor one method in the class to testé o que o autor chama de "risível".

(Pelo menos para mim) Não se trata de 'menos', é de 'mais'

Então deixe-me reformular como eu o entendi:

E o pensamento de testar cada um de seus métodos com SOMENTE UM MÉTODO (seu próprio método de teste em um relacionamento de 1 a 1) será risível.

Para citar sua cotação novamente:

Quando você percebe que se trata de especificar comportamento e não de escrever testes, seu ponto de vista muda.


Quando você pratica TDD , não pensa :

Eu tenho um método calculateX($a, $b);e ele precisa de um teste testCalculcateXque teste TUDO sobre o método.

O que o TDD diz é para pensar sobre como o seu código DEVE FAZER :

Preciso calcular o maior dos dois valores ( primeiro caso de teste! ), Mas se $ a for menor que zero, ele deverá gerar um erro ( segundo caso de teste! ) E se $ b for menor que zero, deverá ... ( terceiro caso de teste! ) e assim por diante.


Você deseja testar comportamentos, não apenas métodos únicos sem contexto.

Dessa forma, você obtém um conjunto de testes que é documentação para o seu código e REALMENTE explica o que é esperado que ele faça, talvez até o porquê :)


Como você decide para qual parte do seu código você cria testes de unidade?

Bem, tudo o que acaba no repositório ou em qualquer lugar perto da produção precisa de um teste. Eu não acho que o autor de suas citações discordaria disso, como tentei declarar acima.

Se você não tiver um teste, fica muito mais difícil (mais caro) alterar o código, especialmente se você não estiver fazendo a alteração.

O TDD é uma maneira de garantir que você tenha testes para TUDO, mas contanto que você os escreva, tudo bem. Normalmente, escrevê-las no mesmo dia ajuda, já que você não fará isso mais tarde, vai? :)



Resposta aos comentários:

uma quantidade decente de métodos não pode ser testada em um contexto específico porque eles dependem ou dependem de outros métodos

Bem, existem três coisas que esses métodos podem chamar:

Métodos públicos de outras classes

Podemos zombar de outras classes e definir o estado lá. Estamos no controle do contexto, então isso não é um problema.

* Métodos protegidos ou privados no mesmo *

Qualquer coisa que não faça parte da API pública de uma classe não é testada diretamente, geralmente.

Você deseja testar o comportamento e não a implementação e, se uma classe faz tudo, funciona em um grande método público ou em muitos métodos protegidos menores chamados de implementação . Você deseja alterar esses métodos protegidos sem tocar nos seus testes. Porque seus testes serão interrompidos se o seu código mudar de comportamento! É para isso que servem seus testes, para dizer quando você quebra alguma coisa :)

Métodos públicos na mesma classe

Isso não acontece com muita frequência, acontece? E se gostar do exemplo a seguir, existem algumas maneiras de lidar com isso:

$stuff = new Stuff();
$stuff->setBla(12);
$stuff->setFoo(14);
$stuff->execute(); 

Que os setters existem e não fazem parte da assinatura do método execute é outro tópico;)

O que podemos testar aqui é se as execuções explodem quando definimos os valores errados. Isso setBlagera uma exceção quando você passa uma string e pode ser testado separadamente, mas se quisermos testar se esses dois valores permitidos (12 e 14) não funcionam JUNTOS (por qualquer motivo) do que esse é um caso de teste.

Se você deseja uma suíte de testes "boa", pode, em php, talvez (!) Adicionar uma @covers Stuff::executeanotação para garantir que você gere apenas cobertura de código para esse método e que outras coisas que estão apenas configuradas precisam ser testadas separadamente (novamente, se você quer isto).

Portanto, o ponto é: talvez você precise criar parte do mundo circundante primeiro, mas deve ser capaz de escrever casos de teste significativos que geralmente abrangem apenas uma ou talvez duas funções reais (setters não contam aqui). O restante pode ser zombado do éter ou ser testado primeiro e depois confiar (ver @depends)


* Nota: A questão foi migrada do SO e, inicialmente, era sobre PHP / PHPUnit, é por isso que o código de exemplo e as referências são do mundo php, acho que isso também é aplicável a outras linguagens, pois o phpunit não difere muito dos outros xUnit estruturas de teste.


Muito detalhado e informativo ... Você disse: "Você deseja testar comportamentos, não apenas métodos isolados sem contexto". Certamente, uma quantidade decente de métodos não pode ser testada dentro de um contexto específico porque eles dependem ou dependem de outros métodos. , portanto, uma condição de teste útil só seria contextual se os dependentes estivessem sendo testados também? Ou estou interpretando mal o que você quer dizer>
zcourts

@ robinsonc494 Vou editar em um exemplo que talvez explica um pouco melhor onde eu estou indo com isso
edorian

obrigado pelas edições e exemplo, certamente ajuda. Eu acho que minha confusão (se é que você pode chamar assim) foi que, embora eu tenha lido sobre o teste de "comportamento", de alguma forma eu apenas pensei inerentemente (talvez?) Em casos de teste focados na implementação.
Zcourts 06/07

@ robinsonc494 Talvez pense assim: se você der um soco em alguém que o éter devolve, chame a polícia ou fuja. Esse comportamento. É o que a Pessoa faz. O fato de ele usar seus mexilhões desencadeados por pequenas cargas elétricas do cérebro é uma implementação. Se você quiser testar a reação de alguém, dê um soco nele e veja se ele age como você espera. Você não o coloca em um scanner de cérebro e vê se os impulsos são enviados aos mexilhões. Alguns vai para as classes muito bonito;)
edorian

3
Acho que o melhor exemplo que vi do TDD que realmente ajudou a clicar em como escrever os casos de teste foi o Bowling Game Kata do tio Bob Martin. slideshare.net/lalitkale/bowling-game-kata-by-robert-c-martin
Amy Anuszewski

2

Testes e testes unitários não são a mesma coisa. O Teste de Unidade é um subconjunto muito importante e interessante de Testes em geral. Eu diria que o foco do teste de unidade nos faz pensar sobre esse tipo de teste de maneiras que contradizem as citações acima.

Primeiro, se seguirmos o TDD ou mesmo o DTH (ou seja, desenvolver e testar em estreita harmonia), estaremos usando os testes que escrevemos como foco para obter o design correto. Pensando nos casos de canto e escrevendo testes de acordo, evitamos que os bugs entrem em primeiro lugar, então, na verdade, escrevemos o teste que esperamos passar (OK, no início do TDD, eles falham, mas isso é apenas um artefato de pedido, quando o código está pronto, esperamos que eles sejam aprovados, e a maioria aceita, porque você pensou no código.

Segundo, os testes unitários realmente surgem quando refatamos. Alteramos nossa implementação, mas esperamos que as respostas permaneçam as mesmas - o Teste de Unidade é nossa proteção contra a quebra de nosso contrato de interface. Então, mais uma vez, esperamos que os testes passem.

Isso implica que, para nossa interface pública, que provavelmente é estável, precisamos de uma rastreabilidade clara, para que possamos ver que todos os métodos públicos são testados.

Para responder sua pergunta explícita: Teste de unidade para interface pública tem valor.

editado no comentário da resposta:

Testando métodos particulares? Sim, deveríamos, mas se tivermos que não testar algo, é aí que eu comprometeria. Afinal, se os métodos públicos estão funcionando, esses bugs podem ser tão importantes? Pragmaticamente, a rotatividade tende a acontecer nas coisas particulares, você trabalha duro para manter sua interface pública, mas se as coisas de que depende mudar as coisas particulares podem mudar. Em algum momento, podemos achar que manter os testes internos é muito trabalhoso. Esse esforço é bem gasto?


Portanto, apenas para ter certeza de que entendi o que você está dizendo: ao testar, concentre-se em testar a interface pública, certo? Supondo que isso esteja correto ... não há uma possibilidade maior de deixar bugs em métodos / interfaces particulares para os quais você não fez testes de unidade, alguns bugs complicados na interface "privada" não testada podem levar à aprovação de um teste quando realmente deveria ter falhado. Estou errado em pensar assim?
Zcourts 6/07

Usando a cobertura de código, você pode saber quando o código em seus métodos privados não está sendo executado ao testar seus métodos públicos. Se seus métodos públicos forem totalmente cobertos, qualquer método privado descoberto não será utilizado e poderá ser removido. Caso contrário, você precisará de mais testes para seus métodos públicos.
David Harkness 07/07

2

Os testes de unidade devem fazer parte de uma estratégia de teste maior. Sigo esses princípios ao escolher quais tipos de testes escrever e quando:

  • Concentre-se em escrever testes de ponta a ponta. Você cobre mais código por teste do que com testes de unidade e, portanto, obtém mais testes pelo preço. Faça disso a validação automatizada do seu sistema como um todo.

  • Comece a escrever testes de unidade em torno de pepitas de lógica complicada. Os testes de unidade valem o seu peso em situações em que os testes de ponta a ponta seriam difíceis de depurar ou difíceis de escrever para obter uma cobertura de código adequada.

  • Aguarde até que a API em que você está testando esteja estável para gravar qualquer tipo de teste. Você deseja evitar ter que refatorar sua implementação e seus testes.

Rob Ashton tem um bom artigo sobre esse tópico, do qual me baseei bastante para articular os princípios acima.


+1 - Não concordo necessariamente com todos os seus pontos (ou com os pontos do artigo), mas onde concordo é que a maioria dos testes de unidade é inútil se for feita às cegas (abordagem TDD). No entanto, eles são extremamente valiosos se você for inteligente em decidir o que vale a pena gastar tempo fazendo testes de unidade. Concordo inteiramente que você ganha muito, muito mais dinheiro ao escrever testes de nível superior, em particular testes automatizados no nível do subsistema. O problema dos testes automatizados de ponta a ponta é que eles seriam difíceis, se não totalmente impraticáveis, para um sistema que tenha tamanho / complexidade.
quer

0

Eu costumo seguir uma abordagem diferente ao teste de unidade que parece funcionar bem. Em vez de pensar em um teste de unidade como "Testando algum comportamento", penso nele mais como "uma especificação que meu código deve seguir". Dessa forma, você pode basicamente declarar que um objeto deve se comportar de uma certa maneira e, desde que você assuma que em outras partes do seu programa, você pode ter certeza de que é relativamente livre de erros.

Se você está escrevendo uma API pública, isso é extremamente valioso. No entanto, você sempre precisará de uma boa dose de testes de integração de ponta a ponta, porque geralmente não vale a pena se aproximar de uma cobertura de 100% dos testes de unidade e perderá coisas que a maioria consideraria "não testável" pelos métodos de teste de unidade (zombaria, etc)

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.