As afirmações ou testes de unidade são mais importantes?

51

As declarações e os testes de unidade servem como documentação para uma base de código e como um meio de descobrir bugs. As principais diferenças são que afirmações funcionam como verificações de sanidade e veem entradas reais, enquanto testes de unidade são executados em entradas simuladas específicas e são testes contra uma única "resposta correta" bem definida. Quais são os méritos relativos do uso de afirmações versus testes de unidade como o principal meio de verificar a correção? Qual você acha que deveria ser enfatizado mais fortemente?

dsimcha
fonte
4
Eu gosto muito dessa idéia ... tanto que estou fazendo do tópico uma tarefa para o meu curso de teste de software e garantia de qualidade. :-)
Macneil
Outra pergunta é: você deve testar suas afirmações? ;)
mojuba 11/11

Respostas:

43

As declarações são úteis para informar sobre o estado interno do programa . Por exemplo, que suas estruturas de dados têm um estado válido, por exemplo, que uma Timeestrutura de dados não retém o valor de 25:61:61. As condições verificadas por afirmações são:

  • Condições prévias, que garantem que o chamador mantém seu contrato,

  • Pós-condições, que garantem que o receptor mantenha seu contrato e

  • Invariantes, que garantem que a estrutura de dados sempre mantenha alguma propriedade após o retorno da função. Uma invariante é uma condição que é uma pré-condição e uma pós-condição.

Os testes de unidade são úteis para informar sobre o comportamento externo do módulo . Você Stackpode ter um estado consistente após a push()chamada do método, mas se o tamanho da pilha não aumentar em três após ser chamado três vezes, isso é um erro. (Por exemplo, o caso trivial em que a push()implementação incorreta verifica apenas as declarações e saídas.)

A rigor, a principal diferença entre afirmações e testes de unidade é que os testes de unidade possuem dados de teste (valores para executar o programa), enquanto os de afirmações não. Ou seja, você pode executar seus testes de unidade automaticamente, enquanto não pode dizer o mesmo para afirmações. Para o início desta discussão, assumi que você está falando sobre a execução do programa no contexto de testes de funções de ordem superior (que executam todo o programa e não conduzem módulos como testes de unidade). Se você não está falando sobre testes de função automatizados como meio de "ver entradas reais", então claramente o valor está na automação e, portanto, os testes de unidade vencem. Se você está falando sobre isso no contexto de testes de função (automatizados), veja abaixo.

Pode haver alguma sobreposição no que está sendo testado. Por exemplo, a Stackpós-condição de uma pessoa pode realmente afirmar que o tamanho da pilha aumenta em um. Mas há limites para o que pode ser executado nessa afirmação: ele também deve verificar se o elemento superior foi o que acabou de ser adicionado?

Para ambos, o objetivo é aumentar a qualidade. Para testes de unidade, o objetivo é encontrar erros. Para afirmações, o objetivo é facilitar a depuração, observando estados inválidos do programa assim que eles ocorrem.

Observe que nenhuma técnica verifica a correção. De fato, se você realizar testes de unidade com o objetivo de verificar se o programa está correto, provavelmente encontrará um teste desinteressante que sabe que funcionará. É um efeito psicológico: você fará o que for para atingir seu objetivo. Se seu objetivo é encontrar erros, suas atividades refletirão isso.

Ambos são importantes e têm seus próprios propósitos.

[Como nota final sobre as afirmações: para obter o máximo de valor, você precisa usá-las em todos os pontos críticos do seu programa, e não em algumas funções-chave. Caso contrário, a fonte original do problema pode ter sido mascarada e difícil de detectar sem horas de depuração.]

Macneil
fonte
7

Ao falar sobre afirmações, lembre-se de que elas podem ser desativadas ao pressionar um botão.

Exemplo de uma afirmação muito ruim:

char *c = malloc(1024);
assert(c != NULL);

Por que isso é ruim? Porque nenhuma verificação de erro é feita se essa afirmação for ignorada devido à definição de algo como NDEBUG.

Um teste de unidade (provavelmente) apenas falha no código acima. Claro, ele fez o seu trabalho dizendo que algo deu errado, ou fez? Qual a probabilidade malloc()de falha no teste?

As asserções são para fins de depuração quando um programador precisa sugerir que nenhum evento 'normal' causaria o disparo da asserção. malloc()falhar é, de fato, um evento normal, portanto nunca deve ser afirmado.

Existem muitos outros casos em que asserções são usadas em vez de lidar adequadamente com coisas que podem dar errado. É por isso que as asserções têm uma má reputação e por que idiomas como o Go não as incluem.

Os testes de unidade são projetados para informar quando algo que você alterou quebrou outra coisa. Eles foram projetados para evitar que você (na maioria dos casos) passe por todos os recursos do programa (no entanto, os testadores são importantes para lançamentos) toda vez que você faz uma compilação.

Realmente não há nenhuma correlação distinta entre os dois, exceto os dois dizendo que algo deu errado. Pense em uma afirmação como um ponto de interrupção em algo em que você está trabalhando, sem precisar usar um depurador. Pense em um teste de unidade como algo que informa se você quebrou algo em que não está trabalhando.

Tim Post
fonte
3
É por isso que as afirmações sempre devem ser declarações verdadeiras, não testes de erro.
Dominique McDonnell
@DominicMcDonnell Bem, declarações 'devem ser verdadeiras'. Às vezes, afirmo contornar as peculiaridades do compilador, como certas versões do gcc que possuem um buggy abs () embutido. O importante a lembrar é que as compilações de produção devem desativá-las de qualquer maneira .
Tim Post
E aqui eu acho que o código de produção é o lugar que precisa o afirma a maioria , porque é na produção onde você terá entradas que você não pensou possível. A produção é o lugar que expulsa todos os erros mais difíceis.
Frank Shearar
@ Frank Shearar, muito verdadeiro. Você ainda deve estar com falhas graves no estado de erro detectado mais cedo na produção. Claro que os usuários vão reclamar, mas essa é a única maneira de garantir que os bugs sejam corrigidos. E é muito melhor obter um blá como 0 do que uma exceção de memória para remover a referência nula algumas chamadas de função mais tarde.
Dominique McDonnell
por que não é melhor lidar com problemas típicos, mas geralmente incomuns (aos olhos do usuário), em vez de afirmar que eles nunca deveriam acontecer? Não consigo perceber essa sabedoria. Claro, afirme que um gerador principal retorna um número primo, o que exige um pouco de trabalho extra, o que é esperado nas compilações de depuração. Afirmar algo que um idioma pode testar nativamente é simplesmente, bem, estúpido. Não envolver esses testes em outro switch que pode ser desativado alguns meses após o lançamento, ainda mais estúpido.
Tim Post
5

Ambas são ferramentas usadas para ajudar a melhorar a qualidade geral do sistema que você está construindo. Depende muito do idioma que você está usando, do tipo de aplicativo que está criando e de onde é melhor gastar seu tempo. Sem mencionar que você tem algumas escolas de pensamento sobre isso.

Para começar, se você estiver usando um idioma sem uma assertpalavra - chave, não poderá usar declarações (pelo menos não da maneira como estamos falando aqui). Por um longo tempo, o Java não teve uma assertpalavra - chave e várias linguagens ainda não. O teste de unidade torna-se um pouco mais importante. Em algumas linguagens, as asserções são executadas apenas quando um sinalizador é definido (novamente com Java aqui). Quando as proteções nem sempre estão lá, não é um recurso muito útil.

Há uma escola de pensamento que diz que se você está "afirmar" algo, assim como você pode escrever um if/ throwbloco de exceção significativa. Esse processo de pensamento vem de muitas afirmações colocadas no início de um método para garantir que todos os valores estejam dentro dos limites. Testar suas pré-condições é uma parte muito importante de se ter uma pós-condição esperada.

O teste de unidade é um código extra que deve ser escrito e mantido. Para muitos, isso é uma desvantagem. No entanto, com a colheita atual de estruturas de teste de unidade disponíveis, é possível gerar um número maior de condições de teste com comparativamente pouco código. Testes e "teorias" parametrizados realizarão o mesmo teste com um grande número de amostras de dados que podem descobrir alguns erros difíceis de encontrar.

Pessoalmente, acho que obtenho mais quilometragem com testes de unidade do que com declarações de aspersão, mas isso se deve às plataformas que desenvolvo na maioria das vezes (Java / C #). Outros idiomas têm um suporte de afirmação mais robusto e até o "Design by Contract" (veja abaixo) para fornecer ainda mais garantias. Se eu estivesse trabalhando com um desses idiomas, poderia usar o DBC mais do que o teste de unidade.

http://en.wikipedia.org/wiki/Design_by_contract#Languages_with_native_support

Berin Loritsch
fonte