Como escrever testes de unidade "bons"?

9 respostas

53

A Arte de Testes Unitários tem o seguinte a dizer sobre testes unitários:

A unit test should have the following properties:

  • It should be automated and repeatable.
  • It should be easy to implement.
  • Once it’s written, it should remain for future use.
  • Anyone should be able to run it.
  • It should run at the push of a button.
  • It should run quickly.

e depois adiciona que deve ser totalmente automatizado, confiável, legível e sustentável.

Eu recomendo strongmente que você leia este livro se ainda não o fez.

Na minha opinião, tudo isso é muito importante, mas os três últimos (confiáveis, legíveis e sustentáveis), especialmente, como se seus testes tivessem essas três propriedades, então o seu código normalmente também os possui.

    
por 24.11.2010 / 14:58
fonte
42

Um bom teste de unidade não reflete a função que está testando.

Como um exemplo muito simplificado, considere que você tem uma função que retorna uma média de dois int's. O teste mais abrangente chamaria a função e verificaria se um resultado é de fato uma média. Isso não faz sentido algum: você está espelhando (replicando) a funcionalidade que está testando. Se você cometeu um erro na função principal, você cometerá o mesmo erro no teste.

Em outras palavras, se você se encontrar replicando a principal funcionalidade no teste de unidade, é um sinal provável de que você está desperdiçando seu tempo.

    
por 24.11.2010 / 11:28
fonte
10

Bons testes unitários são essencialmente a especificação em formato executável:

  1. descreve o comportamento do código correspondente aos casos de uso
  2. cobrem casos técnicos de canto (o que acontece se null for passado) - se um teste não estiver presente para um caso de canto, o comportamento é indefinido.
  3. quebra se o código testado mudar para longe da especificação

Eu achei o Test-Driven-Development muito bem adequado para rotinas de bibliotecas, já que você essencialmente escreve a API primeiro, e ENTÃO a implementação real.

    
por 24.11.2010 / 11:23
fonte
7

para TDD, testes de teste "bons" recursos que o cliente deseja ; os recursos não correspondem necessariamente às funções e os cenários de teste não devem ser criados pelo desenvolvedor em um vácuo

no seu caso - eu estou supondo - o 'recurso' é que a função de ajuste modela os dados de entrada dentro de uma certa tolerância a erros. Como não tenho ideia do que você está realmente fazendo, estou inventando algo; espero que seja analgésico.

Exemplo de história:

As a [X-Wing Pilot] I want [no more than 0.0001% fit error] so that [the targeting computer can hit the Death Star's exhaust port when moving at full speed through a box canyon]

Então você vai falar com os pilotos (e com o computador de segmentação, se for sensível). Primeiro você fala sobre o que é "normal", depois fala sobre o anormal. Você descobre o que realmente importa nesse cenário, o que é comum, o que é improvável e o que é meramente possível.

Digamos que normalmente você terá uma janela de meio segundo em sete canais de dados de telemetria: velocidade, inclinação, rotação, guinada, vetor de alvo, tamanho do alvo e velocidade alvo, e que esses valores serão constantes ou mudarão linearmente. Anormalmente você pode ter menos canais e / ou os valores podem estar mudando rapidamente. Então, juntos você faz alguns testes, como:

//Scenario 1 - can you hit the side of a barn?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is zero
    and all other values are constant,
Then:
    the error coefficient must be zero

//Scenario 2 - can you hit a turtle?
Given:
    all 7 channels with no dropouts for the full half-second window,
When:
    speed is zero
    and target velocity is less than c
    and all other values are constant,
Then:
    the error coefficient must be less than 0.0000000001/ns

...

//Scenario 42 - death blossom
Given:
    all 7 channels with 30% dropout and a 0.05 second sampling window
When:
    speed is zero
    and position is within enemy cluster
    and all targets are stationary
Then:
    the error coefficient must be less than 0.000001/ns for each target

Agora, você deve ter notado que não há cenário para a situação específica descrita na história. Acontece que depois de conversar com o cliente e outras partes interessadas, esse objetivo na história original era apenas um exemplo hipotético. Os testes reais saíram da discussão que se seguiu. Isso pode acontecer. A história deve ser reescrita, mas não precisa ser [já que a história é apenas um espaço reservado para uma conversa com o cliente].

    
por 24.11.2010 / 16:28
fonte
5

Crie testes para casos de canto, como um conjunto de testes contendo apenas o número mínimo de entradas (possível 1 ou 0) e alguns casos padrão. Esses testes unitários não substituem os testes de aceitação, nem devem ser.

    
por 24.11.2010 / 11:00
fonte
5

Tenho visto muitos casos em que as pessoas investem uma quantidade enorme de esforço escrevendo testes de código que raramente são inseridos e não estão escrevendo testes para códigos inseridos com frequência.

Antes de se sentar para escrever qualquer teste, você deve estar olhando para algum tipo de gráfico de chamada, para garantir que você planeje uma cobertura adequada.

Além disso, eu não acredito em escrever testes apenas para dizer "Sim, nós testamos isso". Se eu estiver usando uma biblioteca que é descartada e permanecerá imutável, não vou desperdiçar um dia escrevendo testes para ter certeza de que as entranhas de uma API nunca mudarão as funções conforme o esperado, mesmo que certas partes dela pontuem no alto de um gráfico de chamadas. Testes que consomem dita biblioteca (meu próprio código) apontam isso.

    
por 24.11.2010 / 11:49
fonte
4

Não é tão TDD, mas depois de entrar no QA você pode melhorar seus testes configurando casos de teste para reproduzir quaisquer erros que surjam durante o processo de QA. Isso pode ser particularmente valioso quando você está entrando em um suporte de longo prazo e começa a chegar a um lugar onde você arrisca a reintrodução inadvertida de bugs antigos. Ter um teste no local para capturar isso é particularmente valioso.

    
por 24.11.2010 / 12:09
fonte
3

Eu tento fazer com que todos os testes testem apenas uma coisa. Eu tento dar a cada teste um nome como shouldDoSomething (). Eu tento testar o comportamento, não a implementação. Eu só testo métodos públicos.

Eu geralmente tenho um ou alguns testes para o sucesso e, em seguida, talvez um punhado de testes para falha, por método público.

Eu uso muito os mock-ups. Uma boa estrutura simulada provavelmente seria bastante útil, como o PowerMock. Embora eu não esteja usando nenhum ainda.

Se a classe A usa outra classe B, eu adicionaria uma interface, X, de modo que A não usasse B diretamente. Então eu criaria o mock-up XMockup e usaria em vez de B nos meus testes. Isso realmente ajuda a acelerar a execução de testes, reduzindo a complexidade do teste e também reduz o número de testes que escrevo para A, já que não tenho que lidar com as peculiaridades de B. Posso, por exemplo, testar que A chama X.someMethod () em vez de um efeito colateral de chamar B.someMethod ().

Mantenha o código de teste limpo também.

Ao usar uma API, como uma camada de banco de dados, eu zombaria dela e ativaria o modelo para lançar uma exceção em todas as oportunidades possíveis no comando. Eu, então, executo os testes sem lançar, e em um loop, cada vez lançando uma exceção na próxima oportunidade até que o teste volte a ocorrer. Um pouco como os testes de memória disponíveis para o Symbian.

    
por 24.11.2010 / 12:43
fonte
2

Eu vejo que Andry Lowry já postou as métricas de teste de unidade de Roy Osherove; mas parece que ninguém apresentou o conjunto (cortesia) que o Tio Bob dá em Código Limpo (132-133). Ele usa a sigla PRIMEIRA (aqui com meus sumários):

  • Rápido (eles devem ser executados rapidamente, para que as pessoas não se importem em executá-los)
  • Independente (os testes não devem configurar ou desmontar um ao outro)
  • Repetível (deve ser executado em todos os ambientes / plataformas)
  • Self-validating (totalmente automatizado; a saída deve ser "pass" ou "fail", não um arquivo de log)
  • Timely (quando escrevê-los - antes de escrever o código de produção que eles testam)
por 29.03.2013 / 18:39
fonte