Como testar os testes?


53

Testamos nosso código para torná-lo mais correto (na verdade, é menos provável que esteja incorreto ). No entanto, os testes também são de código - eles também podem conter erros. E se seus testes são com erros, eles dificilmente melhoram seu código.

Posso pensar em três tipos possíveis de erros nos testes:

  1. Erros lógicos, quando o programador não entendeu a tarefa em questão, e os testes fazem o que ele pensou que deveriam fazer, o que está errado;

  2. Erros na estrutura de teste subjacente (por exemplo, uma abstração de simulação com vazamento);

  3. Erros nos testes: o teste está fazendo um pouco diferente do que o programador pensa que é.

Os erros do tipo (1) parecem impossíveis de evitar (a menos que o programador apenas ... fique mais esperto). No entanto, (2) e (3) podem ser tratáveis. Como você lida com esses tipos de erros? Você tem estratégias especiais para evitá-las? Por exemplo, você escreve alguns testes "vazios" especiais, que apenas verificam os pressupostos do autor do teste? Além disso, como você aborda a depuração de um caso de teste quebrado?


3
Cada artigo introdutório que li sobre zombaria parece ter atingido esse problema. Quando você começa a zombar de coisas, os testes sempre parecem ser mais complicados do que o código que eles estão testando. Obviamente, é menos provável que isso ocorra ao testar o código do mundo real, mas é bastante desanimador quando você está tentando aprender.
Carson63000

@ Carson63000 Se é um teste simples testando algo com uma simulação testada , a complexidade é dividida e está sob controle (eu acho).
mlvljr

13
Mas então como você testa os testes?
Ocodo 20/02

+1. O item 1 pode ser um erro de requisitos. Só pode ser evitado revisando os requisitos. Provavelmente fora das mãos do programador, a menos que eles também são os requisitos analista
MarkJ

@ocodo: Da mesma maneira que você assiste os Watchers. :)
Greg Burghardt

Respostas:


18

Os testes já estão testados. Os testes são protegidos por design dos erros, porque os testes detectam apenas diferenças entre o código e nossas expectativas. Se houver problemas, temos um erro. O erro pode estar no código ou com a mesma probabilidade nos testes.

  1. Existem algumas técnicas que impedem que você adicione o mesmo bug no seu código e nos testes:

    1. O cliente deve ser uma pessoa diferente do implementador.
    2. Primeiro escreva os testes e depois o código (como no Test Driven Development).
  2. Você não precisa testar a plataforma subjacente. Os testes não apenas exercitam o código escrito por você, mas também executam o código a partir da plataforma. Embora você não precise capturar bugs na plataforma de teste, é muito difícil escrever código e testes que sempre ocultam um bug na plataforma, em outras palavras, é muito difícil ter um bug sistemático nos seus testes / código e na plataforma e a probabilidade é reduzida a cada teste que você cria. Mesmo se você tentasse fazer isso, teria uma tarefa muito difícil.

  3. Você pode ter erros nos testes, mas geralmente eles são capturados facilmente porque os testes são testados pelo código desenvolvido. Entre o código e os testes, você tem um feedback de auto-aplicação. Ambos fazem previsões sobre como uma chamada específica de uma interface deve se comportar. Se a resposta for diferente, você não precisa ter um bug no código. Você também pode ter um bug no teste.


Resposta muito boa. Gosto da idéia de um loop auto-reforçado entre os testes e o código e a observação de que seria difícil escrever testes que ocultam consistentemente erros na plataforma subjacente.
Ryszard Szopa

o que não impede a criação de testes com base em suposições falhas sobre o que o código real deve fazer. O que pode levar a que erros muito desagradáveis ​​não sejam detectados durante o teste. A única maneira de evitar isso é ter os testes escritos por terceiros sem nenhuma relação com a organização que escreve o código real, para que eles não possam "poluir" o pensamento um do outro quando se trata de interpretar os documentos de requisitos.
jwenting

24

Tente fazer os testes individuais tão pequenos (curtos) quanto possível.

Isso deve reduzir as chances de criar um bug em primeiro lugar. Mesmo se você conseguir criar um, é mais fácil encontrá-lo. Os testes de unidade devem ser pequenos e específicos, com baixa tolerância a falhas e desvios.

No final, provavelmente é apenas uma questão de experiência. Quanto mais testes você escreve, melhor se torna, menos chance tem de fazer testes ruins.


3
E se os testes precisarem de uma configuração bastante complicada? Às vezes, esse tipo de coisa não está sob seu controle.
Ryszard Szopa

Bem, acho que a configuração complicada é a "condição inicial" dos testes. Se isso falhar, todos os seus testes deverão falhar. Na verdade, estou trabalhando nesse projeto no momento, e as pessoas que nunca usaram testes de unidade perguntaram constantemente a mesma coisa ... até explicarmos o que realmente são testes de unidade :) Então eles perceberam que isso pode ser feito, apesar de tremendo complexidade do projeto.
dr Hannibal Lecter

Qual é a melhor maneira de verificar se essa "condição inicial" é atendida é exatamente o ponto da minha pergunta. Você escreve um teste separado para isso? Ou apenas suponha que os testes serão interrompidos se essa condição não for verdadeira? E a situação em que a configuração não é "catastroficamente" ruim, apenas ligeiramente desligada?
Ryszard Szopa

2
Seus testes devem falhar se as condições iniciais não estiverem corretas, esse é o ponto. Quando no estado A, você espera resultado B. Se você não tem estado A, um teste deve falhar. Nesse ponto, você pode investigar por que falhou, más condições iniciais ou um teste ruim, mas deve falhar nos dois casos. Mesmo que seja, como você diz, "um pouco fora" (ou seja "A" => "B", "a" => "b", mas nunca "a" => "B"ou seu teste é ruim).
dr Hannibal Lecter

19

Uma tática é escrever o teste antes do código testado e garantir que o teste falhe primeiro pelo motivo certo. Se você usa TDD, deve obter pelo menos esse nível de teste de testes.

Uma maneira mais exaustiva de testar a qualidade de um conjunto de testes é usar o teste de mutação .


2
E que seu teste falha pelo motivo certo .
Frank Shearar

@Frank - Sim. Vou acrescentar isso à resposta.
Don Roby

E você está adicionando um novo teste para o novo comportamento a ser testado. Não adicione a testes existentes.
Huperniketes

@ DonRoby, você achou o teste de mutação útil na prática? Quais deficiências você encontrou em seus casos de teste com isso?
dzieciou

4

Para os nºs 1 e 3: Os testes de unidade não devem conter lógica, se você o fizer, provavelmente está testando mais de uma coisa no seu teste de unidade. Uma prática recomendada para testes de unidade é ter apenas um teste por teste de unidade.

Assista a este vídeo de Roy Osherove para saber mais sobre como escrever bem os testes de unidade.


anúncio nº 3 - Concordo que os testes devem ser o mais simples possível e não devem conter lógica. No entanto, pense na fase de configuração do teste, ao criar os objetos necessários. Você pode criar objetos ligeiramente errados. Esse é o tipo de problema em que estou pensando.
Ryszard Szopa

Quando você diz "objetos levemente errados", o estado do objeto não está correto ou o design real do objeto não está correto? Para o estado do objeto, você provavelmente poderia escrever testes para verificar sua validade. Se o design estiver errado, o teste deverá falhar.
Piers Myers

3

Em termos do nº 1 - acho uma boa ideia emparelhar / revisar código para esse lado das coisas. É fácil fazer pressuposições ou simplesmente fazer as coisas erradas, mas se você precisar explicar o que seu teste está fazendo, qual é o objetivo, é mais provável que você atenda se estiver mirando no alvo errado.


2

Deve haver um momento em que se deve parar de tentar o teste de unidade. Deve saber quando traçar a linha. Devemos escrever casos de teste para testar casos de teste? E os novos casos de teste gravados para testar casos de teste? Como vamos testá-los?

if (0 > printf("Hello, world\n")) {
  printf("Printing \"Hello, world\" failed\n");
}

Editar: atualizado com a explicação sugerida pelo comentário.


-1 O que? Isso parece não ter relevância.
alternativa

2
Deve haver um momento em que se deve parar de tentar o teste de unidade. Deve saber quando traçar a linha. Devemos escrever casos de teste para testar casos de teste? E os novos casos de teste gravados para testar casos de teste? Como vamos testá-los?
aufather

2
Processo cerebral levantou EInfiniteRecursion ao tentar extrapolar sua declaração ...
Mason Wheeler

Substitua sua resposta pelo seu comentário e você receberá um +1
Nota para si mesmo - pense em um nome

3
Com toda a justiça, seu exemplo é um homem de palha. Você está testando o subsistema printf () em uma biblioteca C, não o programa real chamando printf (). Concordo, no entanto, que em algum momento é preciso interromper o teste recursivo dos testes.
Tim Post

2

Ei.
Você tem que aplicativos:

  • Seu produto
  • Seu teste para esse produto.

Quando você está executando testes no seu produto, na verdade não está interessado no teste em si, mas na interação entre o produto e os testes. Se o teste falhar, não indica que o aplicativo possui um erro. Diz que a interação entre produto e teste não foi bem-sucedida . Agora é seu trabalho determinar o que deu errado. Pode ser:

  • o aplicativo não está se comportando conforme o esperado (essa expectativa é expressa em seu teste)
  • aplicativo está se comportando corretamente, você simplesmente não documentou esse comportamento corretamente (em seus testes)

Para mim, os testes que falham não são um feedback simples, isso e aquilo estão errados . É um indicador de que há inconsistência, e preciso examinar os dois para verificar se o desejo deu errado. No final, sou responsável por verificar se a aplicação está correta; os testes são apenas uma ferramenta para destacar áreas que podem ser verificadas.

Os testes estão verificando apenas algumas partes do aplicativo. Eu testo a aplicação, eu testo os testes.


2

Os testes não devem ser "inteligentes" o suficiente para abrigar erros.

O código que você está escrevendo implementa um conjunto de especificações. (Se X, então Y, a menos que Z, nesse caso Q, etc etc). Todo o teste que você está tentando realizar é determinar que X realmente é Y, a menos que Z, nesse caso Q. Isso significa que tudo o que um teste deve fazer é definir X e verificar Y.

Mas isso não cobre todos os casos, você provavelmente está dizendo e estaria certo. Mas se você tornar o teste "inteligente" o suficiente para saber que X deve apenas por Y, se não Z, estará basicamente reimplementando a lógica de negócios no teste. Isso é problemático por razões que abordaremos um pouco mais abaixo. Você não deve melhorar a cobertura do código tornando seu primeiro teste "mais inteligente"; em vez disso, adicione um segundo teste burro que define X e Z e verifica Q. Dessa forma, você terá dois testes, um que abrange o caso geral ( às vezes também conhecido como caminho feliz) e um que cobre o caso extremo como um teste separado.

Há várias razões para isso. Em primeiro lugar, como você determina se um teste com falha é devido a um erro na lógica de negócios ou a um erro nos testes? Obviamente, a resposta é que, se os testes forem o mais simples possível, é muito improvável que eles estejam abrigando bugs. Se você acha que seus testes precisam ser testados, está testando errado .

Outras razões incluem que você está apenas replicando o esforço (como eu já mencionei, escrever um teste inteligente o suficiente para exercer todas as possibilidades em um único teste é basicamente replicar a lógica de negócios que você está tentando testar em primeiro lugar), se os requisitos mudarem os testes devem ser fáceis de alterar para refletir os novos requisitos, servem como uma espécie de documentação (são uma maneira formal de dizer qual é a especificação da unidade em teste) e assim por diante.

TL: DR: Se seus testes precisam ser testados, você está fazendo errado. Escreva testes idiotas .


0

Não é uma resposta (não tenho o privilégio de comentar), mas estava me perguntando se você esqueceu outras razões para desenvolver casos de teste ...
Depois de descobrir todos os erros nos testes, você pode fazer o teste de regressão com facilidade. Os conjuntos de testes automatizados ajudariam a encontrar problemas antes, antes da integração. As alterações nos requisitos são relativamente mais fáceis de serem testadas, pois elas podem se tornar versões mais recentes e alteradas dos casos de teste mais antigos que passam e os casos mais antigos permanecem para detectar falhas.


0

Resposta curta: O código de produção testa os testes .

Compare isso com o modelo de crédito / débito usado em economia. A mecânica é muito simples - se o crédito diferir do débito, há algo errado.

o mesmo vale para testes de unidade - Se um teste falhar, isso indica que algo está errado. Pode ser o código de produção, mas também pode ser o código de teste! Esta última parte, se importante.

Observe que seus erros do tipo (1) não podem ser encontrados por testes de unidade. Para evitar esse tipo de erro, você precisa de outras ferramentas.

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.