Repensando as afirmações de teste de unidade

Eric Elliott Blocked Unblock Seguir Seguindo 8 de outubro de 2018

Testes automatizados bem escritos sempre funcionam como um bom relatório de bug quando eles falham, mas poucos desenvolvedores gastam tempo para pensar sobre quais informações um bom relatório de erros precisa.

Existem 5 perguntas para cada teste de unidade . Eu os descrevi detalhadamente antes, então vamos dar uma olhada neles desta vez:

  1. O que é a unidade em teste (módulo, função, classe, o que for)?
  2. O que deveria fazer? (Descrição da prosa)
  3. Qual foi a saída real?
  4. Qual foi o resultado esperado?
  5. Como você reproduz o fracasso?

Muitas estruturas de teste permitem que você ignore uma ou mais dessas perguntas, e isso leva a relatórios de bugs que não são muito úteis.

Vamos dar uma olhada neste exemplo usando uma estrutura de teste fictícia que fornece as asserções pass() e fail() geralmente fornecidas:

 describe ('addEntity ()', assíncrono ({pass, fail}) => { 
const myEntity = {id: 'baz', foo: 'bar'};
 experimentar { 
const resposta = aguarde addEntity (myEntity);
const storedEntity = aguardar getEntity (response.id);
pass ('deve adicionar a nova entidade');
} pegar (err) {
fail ('falhou em adicionar e ler entidade', {myEntity, error});
}
});

Estamos no caminho certo aqui, mas faltam algumas informações. Vamos tentar responder as 5 perguntas usando os dados disponíveis neste teste:

  1. O que é a unidade em teste? addEntity()
  2. O que deveria fazer? 'should add the new entity'
  3. Qual foi a saída real? Oops Nós não sabemos Nós não fornecemos esses dados para a estrutura de testes.
  4. Qual foi o resultado esperado? Mais uma vez, não sabemos. Não estamos testando um valor de retorno aqui. Em vez disso, estamos assumindo que, se não for lançado, tudo funcionou como esperado – mas e se não funcionasse? Devemos estar testando o valor resultante se a função retornar um valor ou uma promessa de resolução.
  5. Como você reproduz o fracasso? Podemos ver isso um pouco na configuração do teste, mas podemos ser mais explícitos sobre isso. Por exemplo, seria bom ter uma descrição em prosa da entrada que você está alimentando para nos dar uma melhor compreensão da intenção do caso de teste.

Eu marcaria isso em 2,5 de 5. Falha. Este teste não está fazendo o seu trabalho. Obviamente, não está respondendo às 5 perguntas que todo teste de unidade deve responder.

O problema com a maioria das estruturas de teste é que eles estão tão ocupados facilitando que você tome atalhos com suas asserções “convenientes” que eles esquecem que o maior valor de um teste é percebido quando o teste falha.

No estágio de falha, a conveniência de escrever o teste é muito menos importante do que a facilidade em descobrir o que deu errado quando lemos o teste.

Em "5 perguntas que cada teste de unidade deve responder" , eu escrevi:

equal() é minha afirmação favorita. Se a única afirmação disponível em todos os conjuntos de testes fosse igual (), quase todos os conjuntos de testes do mundo seriam melhores para isso. ”

Nos anos desde que eu escrevi isso, eu me dediquei a essa crença. Enquanto os frameworks de teste se ocupavam em adicionar ainda mais afirmações “convenientes”, escrevi um wrapper fino em torno de Tape que apenas expunha uma afirmação de igualdade profunda. Em outras palavras, peguei a biblioteca de fitas já mínima e removi recursos para melhorar a experiência de teste.

Liguei para a biblioteca wrapper “ RITEway ” depois dos princípios de teste do RITE Way. Os testes devem ser:

  • R eadable
  • Eu solated (para testes de unidade) ou I ntegrated (para testes funcionais e de integração, o teste deve ser isolado e os componentes / módulos devem ser integrados)
  • T horough, e
  • E xplicito

O RITEway obriga você a escrever testes Readable, Isolated e Explicit, porque essa é a única maneira de usar a API. Também torna mais fácil ser aprofundado fazendo afirmações de teste tão simples que você desejará escrever mais delas.

Aqui está a assinatura do assert() do RITEway:

 afirmar({ 
dado: Qualquer,
deveria: String,
atual: qualquer,
esperado: Qualquer
}) => Vazio

A afirmação deve estar em um bloco describe() que leva um rótulo para a unidade em teste como o primeiro parâmetro. Um teste completo é assim:

 describe ('sum ()', afirmação assíncrona => { 
afirmar({
dado: 'sem argumentos',
deve: 'retornar 0',
real: sum (),
esperado: 0
});
});

Que produz o seguinte:

 TAP version 13 
# sum()
ok 1 Given no arguments: should return 0

Vamos dar uma olhada no nosso teste de 2,5 estrelas acima e ver se podemos melhorar nossa pontuação:

 describe ('addEntity ()', afirmação assíncrona => { 
const myEntity = {id: 'baz', foo: 'bar'};
const given = 'uma entidade';
const deveria = 'ler a mesma entidade da api';
 experimentar { 
const resposta = aguarde addEntity (myEntity);
const storedEntity = aguardar getEntity (response.id);
 afirmar({ 
dado,
devemos,
real: storedEntity,
esperado: myEntity
});
} pegar (erro) {
afirmar({
dado,
devemos,
real: erro
esperado: myEntity
});
}
});
  1. O que é a unidade em teste? addEntity()
  2. O que deveria fazer? 'given an entity: should read the same entity from the api'
  3. Qual foi a saída real? { id: 'baz', foo: 'bar' }
  4. Qual foi o resultado esperado? { id: 'baz', foo: 'bar' }
  5. Como você reproduz o fracasso? Agora as instruções para reproduzir o teste são explicitamente explicitadas na mensagem: As descrições fornecidas e devem ser fornecidas.

Agradável! Agora estamos passando no teste.

É uma afirmação de igualdade profunda realmente suficiente?

Eu tenho usado o RITEway quase diariamente em vários projetos de produção por quase um ano e meio. Ele evoluiu um pouco. Tornamos a interface ainda mais simples do que era originalmente, mas nunca quis outra afirmação em todo esse tempo, e nossas suítes de teste são as suítes de teste mais simples e legíveis que já vi em toda a minha carreira.

Acho que é hora de compartilhar essa inovação com o resto do mundo. Se você quer começar a usar o RITEway :

 npm install --save-dev riteway 

Isso vai mudar a maneira como você pensa sobre o teste de software.

Em resumo:

Testes simples são melhores testes.

PS Eu tenho usado o termo “testes unitários” ao longo deste artigo, mas isso é apenas porque é mais fácil digitar do que “testes automatizados de software” ou “testes unitários e testes funcionais e testes de integração”, mas tudo que eu disse sobre unidade testes neste artigo se aplicam a todos os testes de software automatizados que eu possa imaginar. Eu gosto desses testes muito melhor do que Pepino / Pepino para testes funcionais também.

Próximos passos

Lições em vídeo sobre desenvolvimento orientado a testes estão disponíveis para membros do EricElliottJS.com. Se você não é um membro, inscreva-se hoje .

Texto original em inglês.