Então, eu tenho um módulo de autenticação que escrevi há algum tempo. Agora estou vendo os erros do meu caminho e escrevendo testes de unidade para ele. Enquanto escrevia testes de unidade, tenho dificuldade em encontrar bons nomes e boas áreas para testar. Por exemplo, eu tenho coisas como
- RequerLogin_should_redirect_when_not_logged_in
- RequerLogin_should_pass_through_when_logged_in
- Login_should_work_when_given_proper_credentials
Pessoalmente, acho que é um pouco feio, mesmo que pareça "adequado". Também tenho problemas para diferenciar os testes, apenas analisando-os (tenho que ler o nome do método pelo menos duas vezes para saber o que acabou de falhar)
Então, pensei que talvez, em vez de escrever testes que testam puramente a funcionalidade, talvez escreva um conjunto de testes que cubram cenários.
Por exemplo, este é um esboço de teste que eu criei:
public class Authentication_Bill
{
public void Bill_has_no_account()
{ //assert username "bill" not in UserStore
}
public void Bill_attempts_to_post_comment_but_is_redirected_to_login()
{ //Calls RequiredLogin and should redirect to login page
}
public void Bill_creates_account()
{ //pretend the login page doubled as registration and he made an account. Add the account here
}
public void Bill_logs_in_with_new_account()
{ //Login("bill", "password"). Assert not redirected to login page
}
public void Bill_can_now_post_comment()
{ //Calls RequiredLogin, but should not kill request or redirect to login page
}
}
Isso é ouvido de um padrão? Eu já vi histórias de aceitação e coisas assim, mas isso é fundamentalmente diferente. A grande diferença é que eu estou criando cenários para "forçar" os testes. Em vez de tentar manualmente criar possíveis interações que precisarei testar. Além disso, eu sei que isso incentiva testes de unidade que não testam exatamente um método e classe. Eu acho que isso está bem. Além disso, estou ciente de que isso causará problemas para pelo menos algumas estruturas de teste, pois elas geralmente assumem que os testes são independentes um do outro e a ordem não importa (onde seria neste caso).
Enfim, esse é um padrão aconselhável? Ou isso seria um ajuste perfeito para testes de integração da minha API, e não como testes "unitários"? Isso é apenas em um projeto pessoal, então estou aberto a experimentos que podem ou não correr bem.
_test
anexo e uso comentários para anotar os resultados esperados. Se for um projeto pessoal, encontre algum estilo com o qual se sinta confortável e cumpra-o.Respostas:
Sim, é uma boa idéia dar aos seus testes nomes dos cenários de exemplo que você está testando. E usar sua ferramenta de teste de unidade para mais do que apenas testes de unidade, talvez seja bom também, muitas pessoas fazem isso com sucesso (eu também).
Mas não, definitivamente não é uma boa ideia escrever seus testes de uma maneira que importe a ordem de execução dos testes. Por exemplo, o NUnit permite ao usuário selecionar interativamente qual teste ele / ela deseja executar, para que isso não funcione mais da maneira pretendida.
Você pode evitar isso facilmente aqui, separando a parte principal de teste de cada teste (incluindo a "afirmação") das partes que colocam seu sistema no estado inicial correto. Usando seu exemplo acima: escreva métodos para criar uma conta, faça logon e poste um comentário - sem nenhuma afirmação. Em seguida, reutilize esses métodos em diferentes testes. Você também precisará adicionar algum código ao
[Setup]
método dos equipamentos de teste para garantir que o sistema esteja em um estado inicial definido corretamente (por exemplo, nenhuma conta até o momento no banco de dados, ninguém conectado até o momento etc.).EDIT: Claro, isso parece contrariar a natureza da "história" dos seus testes, mas se você der nomes significativos aos métodos auxiliares, encontrará suas histórias em cada teste.
Portanto, deve ficar assim:
fonte
Um problema ao contar uma história com testes de unidade é que não torna explícito que os testes de unidade devem ser organizados e executados de forma totalmente independente um do outro.
Um bom teste de unidade deve ser completamente isolado de todos os outros códigos dependentes; é a menor unidade de código que pode ser testada.
Isso oferece o benefício de que, além de confirmar o código, se um teste falhar, você obtém o diagnóstico exatamente onde o código está errado gratuitamente. Se um teste não é isolado, é necessário analisar o que depende para descobrir exatamente o que deu errado e perder um grande benefício do teste de unidade. A ordem de execução também pode gerar muitos falsos negativos, se um teste falhar, é possível que os testes a seguir falhem, apesar do código que eles testam funcionando perfeitamente.
Um bom artigo com mais profundidade é o clássico dos testes híbridos sujos .
Para tornar as classes, métodos e resultados legíveis, o excelente teste Art of Unit usa a convenção de nomenclatura
Classe de teste:
Métodos de teste:
Para copiar o exemplo do @Doc Brown, em vez de usar [Setup], que é executado antes de cada teste, escrevo métodos auxiliares para criar objetos isolados para testar.
Portanto, os testes com falha têm um nome significativo que fornece uma narrativa sobre exatamente qual método falhou, a condição e o resultado esperado.
É assim que eu sempre escrevi testes de unidade, mas um amigo teve muito sucesso com Gerkin .
fonte
O que você está descrevendo parece mais com o Behavior Driven Design (BDD) do que o teste de unidade para mim. Dê uma olhada no SpecFlow, que é uma tecnologia .NET BDD baseada no DSL da Gherkin .
Coisas poderosas que qualquer humano pode ler / escrever sem saber nada sobre codificação. Nossa equipe de teste está obtendo grande sucesso aproveitando-a para nossos conjuntos de testes de integração.
Em relação às convenções para testes de unidade, a resposta do @ DocBrown parece sólida.
fonte
assert(value === expected)
BDD =value.should.equals(expected)
+ você descreve os recursos em camadas que resolvem o problema da "independência do teste de unidade". Este é um ótimo estilo!