O que faz um bom teste de unidade? [fechadas]

97

Tenho certeza de que a maioria de vocês está escrevendo muitos testes automatizados e que também se deparou com algumas armadilhas comuns durante os testes de unidade.

Minha pergunta é: você segue alguma regra de conduta para redigir testes, a fim de evitar problemas no futuro? Para ser mais específico: quais são as propriedades de bons testes de unidade ou como você escreve seus testes?

Sugestões agnósticas de idioma são encorajadas.

Spoike
fonte

Respostas:

93

Deixe-me começar conectando as fontes - Teste de Unidade Pragmático em Java com JUnit (há uma versão com C # -Nunit também .. mas eu tenho esta .. é agnóstica na maior parte. Recomendado.)

Bons testes devem ser A TRIP (a sigla não é pegajosa o suficiente - eu tenho uma impressão da folha de cheats no livro que eu tive que puxar para ter certeza de que entendi direito ..)

  • Automático : a chamada de testes, bem como a verificação de resultados para APROVAÇÃO / FALHA, deve ser automática
  • Completo : Cobertura; Embora os bugs tendam a se agrupar em torno de certas regiões no código, certifique-se de testar todos os caminhos e cenários principais. Use ferramentas se precisar conhecer regiões não testadas
  • Repetível : os testes devem produzir os mesmos resultados todas as vezes ... todas as vezes. Os testes não devem depender de parâmetros incontroláveis.
  • Independente : Muito importante.
    • Os testes devem testar apenas uma coisa de cada vez. Múltiplas asserções estão bem, desde que todas estejam testando um recurso / comportamento. Quando um teste falha, ele deve apontar a localização do problema.
    • Os testes não devem depender uns dos outros - isolados. Sem suposições sobre a ordem de execução do teste. Certifique-se de 'lousa limpa' antes de cada teste usando configuração / desmontagem de forma adequada
  • Profissional : a longo prazo, você terá tanto código de teste quanto produção (se não mais), portanto, siga o mesmo padrão de bom design para seu código de teste. Métodos bem fatorados - classes com nomes reveladores de intenções, sem duplicação, testes com bons nomes, etc.

  • Bons testes também são executados rapidamente . qualquer teste que leve mais de meio segundo para ser executado .. precisa ser trabalhado. Quanto mais tempo o conjunto de testes leva para ser executado ... menos freqüentemente ele será executado. Quanto mais mudanças o desenvolvedor tentará fazer entre as execuções .. se alguma coisa quebrar .. levará mais tempo para descobrir qual mudança foi a culpada.

Atualização 2010-08:

  • Legível : pode ser considerado parte do Professional - no entanto, não pode ser enfatizado o suficiente. Um teste ácido seria encontrar alguém que não faz parte de sua equipe e pedir-lhe para descobrir o comportamento sob teste em alguns minutos. Os testes precisam ser mantidos da mesma forma que o código de produção - portanto, facilite a leitura, mesmo que exija mais esforço. Os testes devem ser simétricos (seguir um padrão) e concisos (testar um comportamento de cada vez). Use uma convenção de nomenclatura consistente (por exemplo, o estilo TestDox). Evite bagunçar o teste com "detalhes incidentais" .. torne-se minimalista.

Além dessas, a maioria das outras são diretrizes que reduzem o trabalho de baixo benefício: por exemplo, 'Não teste código que você não possui' (por exemplo, DLLs de terceiros). Não saia para testar getters e setters. Fique de olho na relação custo-benefício ou probabilidade de defeito.

Gishu
fonte
Podemos discordar sobre o uso de Mocks, mas este foi um ótimo artigo sobre as melhores práticas de teste de unidade.
Justin Standard
Vou colocar essa aqui como uma resposta porque acho o acrônimo "A TRIP" útil.
Spoike de
3
Eu concordo na maior parte, mas gostaria de salientar que há uma vantagem em testar o código que você não possui ... Você está testando se ele atende aos seus requisitos. De que outra forma você pode ter certeza de que uma atualização não vai quebrar seus sistemas? (Mas é claro, mantenha as relações custo / benefício em mente ao fazer isso.)
Desiludido
@Craig - Acredito que você esteja se referindo a (nível de interface) testes de regressão (ou testes de aluno em alguns casos), que documentam o comportamento do qual você depende. Eu não escreveria testes de 'unidade' para código de terceiros porque a. o fornecedor sabe mais sobre esse código do que eu b. O fornecedor não é obrigado a preservar nenhuma implementação específica. Eu não controlo as alterações nessa base de código e não quero perder meu tempo consertando testes corrompidos com uma atualização. Portanto, prefiro codificar alguns testes de regressão de alto nível para o comportamento que uso (e quero ser notificado quando
houver falha
@Gishu: Sim, absolutamente! Os testes devem ser feitos apenas no nível da interface; e, na verdade, você deve no máximo testar os recursos que realmente usa. Além disso, ao escolher com o que escrever esses testes; Eu descobri que as estruturas de teste de 'unidade' simples e diretas geralmente se encaixam perfeitamente.
Desiludido
42
  1. Não escreva testes gigantescos. Como a 'unidade' em 'teste de unidade' sugere, faça cada um o mais atômico e isolado possível. Se for necessário, crie pré-condições usando objetos fictícios, em vez de recriar muito do ambiente de usuário típico manualmente.
  2. Não teste coisas que obviamente funcionam. Evite testar as classes de um fornecedor terceirizado, especialmente aquele que fornece as APIs principais da estrutura em que você codifica. Por exemplo, não teste a adição de um item à classe Hashtable do fornecedor.
  3. Considere o uso de uma ferramenta de cobertura de código como o NCover para ajudar a descobrir casos extremos que você ainda precisa testar.
  4. Tente escrever o teste antes da implementação. Pense no teste como mais uma especificação que sua implementação seguirá. Cf. também desenvolvimento orientado por comportamento, um ramo mais específico do desenvolvimento dirigido por teste.
  5. Ser consistente. Se você escrever apenas testes para parte do seu código, dificilmente será útil. Se você trabalha em equipe e alguns ou todos os outros não escrevem testes, também não é muito útil. Convença a si mesmo e a todos da importância (e propriedades que economizam tempo ) de testar, ou não se preocupe.
Sören Kuklau
fonte
1
Boa resposta. Mas não é tão ruim se você não fizer um teste de unidade para tudo em uma entrega. Claro que é preferível, mas é preciso haver equilíbrio e pragmatismo. Re: conseguir seus colegas a bordo; às vezes você só precisa fazer isso para demonstrar valor e como um ponto de referência.
Martin Clarke
1
Concordo. No entanto, a longo prazo, você precisa ser capaz de confiar na existência de testes, ou seja, capaz de assumir que armadilhas comuns serão detectadas por eles. Caso contrário, os benefícios diminuem enormemente.
Sören Kuklau
2
"Se você escrever testes apenas para parte do seu código, dificilmente será útil." É este realmente o caso? Eu tenho projetos com 20% de cobertura de código (áreas cruciais / propensas a falhas) e eles me ajudaram enormemente, e os projetos também estão bem.
dr. mal
1
Eu concordo com Slough. Mesmo que haja apenas alguns testes, por serem bem escritos e isolados o suficiente, eles ajudarão tremendamente.
Spoike 01 de
41

A maioria das respostas aqui parece abordar as práticas recomendadas de teste de unidade em geral (quando, onde, por que e o quê), em vez de realmente escrever os próprios testes (como). Como a pergunta parecia bem específica na parte "como", pensei em postar isso, tirado de uma apresentação "bolsa marrom" que fiz na minha empresa.

Testes das 5 Leis da Escrita de Womp:


1. Use nomes de métodos de teste longos e descritivos.

   - Map_DefaultConstructorShouldCreateEmptyGisMap()
   - ShouldAlwaysDelegateXMLCorrectlyToTheCustomHandlers()
   - Dog_Object_Should_Eat_Homework_Object_When_Hungry()

2. Escreva seus testes em um estilo Arrange / Act / Assert .

  • Embora essa estratégia organizacional já exista há um certo tempo e tenha muitos nomes, a introdução da sigla "AAA" recentemente foi uma ótima maneira de passar isso. Tornar todos os seus testes consistentes com o estilo AAA os torna fáceis de ler e manter.

3. Sempre forneça uma mensagem de falha com seus Asserts.

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • Uma prática simples, porém gratificante, que torna óbvio em seu aplicativo runner o que falhou. Se você não fornecer uma mensagem, normalmente obterá algo como "Esperado verdadeiro, era falso" em sua saída de falha, o que o faz realmente ler o teste para descobrir o que está errado.

4. Comente o motivo do teste - qual é a premissa do negócio?

  /// A layer cannot be constructed with a null gisLayer, as every function 
  /// in the Layer class assumes that a valid gisLayer is present.
  [Test]
  public void ShouldNotAllowConstructionWithANullGisLayer()
  {
  }
  • Isso pode parecer óbvio, mas essa prática protegerá a integridade de seus testes de pessoas que não entendem a razão por trás do teste em primeiro lugar. Já vi muitos testes serem removidos ou modificados que estavam perfeitamente bem, simplesmente porque a pessoa não entendeu as suposições que o teste estava verificando.
  • Se o teste for trivial ou o nome do método for suficientemente descritivo, pode ser permitido deixar o comentário desativado.

5. Cada teste deve sempre reverter o estado de qualquer recurso que toque

  • Use simulações sempre que possível para evitar lidar com recursos reais.
  • A limpeza deve ser feita no nível de teste. Os testes não devem depender da ordem de execução.
womp
fonte
2
+1 por causa do ponto 1, 2 e 5 são importantes. 3 e 4 parecem um tanto excessivos para testes de unidade, se você já estiver usando nomes de métodos de teste descritivos, mas eu recomendo a documentação de testes se eles forem grandes em escopo (teste funcional ou de aceitação).
Spoike
+1 para conhecimento prático e prático e exemplos
Fil
17

Mantenha esses objetivos em mente (adaptado do livro xUnit Test Patterns de Meszaros)

  • Os testes devem reduzir o risco, não introduzi-lo.
  • Os testes devem ser fáceis de executar.
  • Os testes devem ser fáceis de manter à medida que o sistema evolui em torno deles

Algumas coisas para tornar isso mais fácil:

  • Os testes só devem falhar por um motivo.
  • Os testes devem testar apenas uma coisa
  • Minimize as dependências de teste (sem dependências de bancos de dados, arquivos, interface do usuário etc.)

Não se esqueça que você pode fazer testes de integração com o seu framework xUnit também, mas mantenha os testes de integração e testes de unidade separados

Mendelt
fonte
Acho que você quis dizer que adaptou do livro "Padrões de teste xUnit", de Gerard Meszaros. xunitpatterns.com
Spoike
Sim, você está certo. Vou esclarecer isso no post
Mendelt
Pontos excelentes. Os testes de unidade podem ser muito úteis, mas são muito importantes para evitar cair na armadilha de ter testes de unidade complexos e interdependentes, que criam uma grande taxa para qualquer tentativa de alterar o sistema.
Wedge de
9

Os testes devem ser isolados. Um teste não deve depender de outro. Além disso, um teste não deve depender de sistemas externos. Em outras palavras, teste seu código, não o código do qual seu código depende. Você pode testar essas interações como parte de seus testes de integração ou funcionais.

Haacked
fonte
9

Algumas propriedades de ótimos testes de unidade:

  • Quando um teste falha, deve ser imediatamente óbvio onde está o problema. Se você tiver que usar o depurador para rastrear o problema, então seus testes não são granulares o suficiente. Ter exatamente uma afirmação por teste ajuda aqui.

  • Ao refatorar, nenhum teste deve falhar.

  • Os testes devem ser executados tão rápido que você nunca hesite em executá-los.

  • Todos os testes devem passar sempre; sem resultados não determinísticos.

  • Os testes de unidade devem ser bem fatorados, assim como seu código de produção.

@Alotor: Se você está sugerindo que uma biblioteca deve ter apenas testes de unidade em sua API externa, eu discordo. Quero testes de unidade para cada classe, incluindo classes que não exponho a chamadores externos. (No entanto, se sentir necessidade de escrever testes para métodos privados, preciso refatorar. )


EDIT: Houve um comentário sobre a duplicação causada por "uma afirmação por teste". Especificamente, se você tiver algum código para configurar um cenário e quiser fazer várias afirmações sobre ele, mas tiver apenas uma afirmação por teste, poderá duplicar a configuração em vários testes.

Eu não adoto essa abordagem. Em vez disso, uso acessórios de teste por cenário . Aqui está um exemplo aproximado:

[TestFixture]
public class StackTests
{
    [TestFixture]
    public class EmptyTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
        }

        [TestMethod]
        [ExpectedException (typeof(Exception))]
        public void PopFails()
        {
            _stack.Pop();
        }

        [TestMethod]
        public void IsEmpty()
        {
            Assert(_stack.IsEmpty());
        }
    }

    [TestFixture]
    public class PushedOneTests
    {
        Stack<int> _stack;

        [TestSetup]
        public void TestSetup()
        {
            _stack = new Stack<int>();
            _stack.Push(7);
        }

        // Tests for one item on the stack...
    }
}
Jay Bazuzi
fonte
Discordo sobre apenas uma afirmação por teste. Quanto mais afirmações você tiver em um teste, menos casos de teste recortar e colar você terá. Acredito que um caso de teste deve se concentrar em um cenário ou caminho de código e as afirmações devem derivar de todas as suposições e requisitos para cumprir esse cenário.
Lucas B,
Acho que concordamos que o DRY se aplica a testes de unidade. Como eu disse, "Os testes de unidade devem ser bem fatorados". No entanto, existem várias maneiras de resolver a duplicação. Uma, como você mencionou, é ter um teste de unidade que primeiro invoca o código em teste e, em seguida, declara várias vezes. Uma alternativa é criar um novo "dispositivo de teste" para o cenário, que invoca o código em teste durante uma etapa de inicialização / configuração e, em seguida, tem uma série de testes de unidade que simplesmente afirmam.
Jay Bazuzi,
Minha regra é, se você estiver usando copiar e colar, você está fazendo algo errado. Um dos meus ditados favoritos é "Copiar e colar não é um padrão de design." Também concordo que uma afirmação por teste de unidade geralmente é uma boa ideia, mas nem sempre insisto nisso. Eu gosto do "teste uma coisa por teste de unidade" mais geral. Embora isso geralmente se traduza em uma declaração por teste de unidade.
Jon Turner
7

O que você quer é delinear os comportamentos da classe em teste.

  1. Verificação dos comportamentos esperados.
  2. Verificação de casos de erro.
  3. Cobertura de todos os caminhos de código dentro da classe.
  4. Exercitar todas as funções de membro da classe.

A intenção básica é aumentar sua confiança no comportamento da classe.

Isso é especialmente útil ao analisar a refatoração de seu código. Martin Fowler tem um interessante artigo sobre testes em seu site.

HTH.

Felicidades,

Roubar

Rob Wells
fonte
Rob - mecânico isso é bom, mas perde a intenção. Por que você fez tudo isso? Pensar dessa maneira pode ajudar outras pessoas no caminho do TDD.
Mark Levison
7

O teste deve falhar originalmente. Então você deve escrever o código que os faz passar, caso contrário, você corre o risco de escrever um teste que está bugado e sempre passa.

Trivial
fonte
@Rismo Não exclusivo em si. Por definição, o que Quarrelsome escreveu aqui é exclusivo da metodologia "Teste Primeiro", que faz parte do TDD. O TDD também leva a refatoração em consideração. A definição mais "esperta" que li é que TDD = Testar Primeiro + Refatorar.
Spoike
Sim, não precisa ser TDD, apenas certifique-se de que seu teste falhe primeiro. Em seguida, conecte o restante. Isso ocorre mais comumente ao fazer TDD, mas você também pode aplicá-lo quando não estiver usando o TDD.
Quibblesome
6

Eu gosto do acrônimo Right BICEP do livro Pragmatic Unit Testing mencionado acima :

  • Certo : os resultados estão certos ?
  • B : são todos os b condições oundary corrigir?
  • I : Podemos verificar i nverse relacionamentos?
  • C : Podemos c resultados ross-seleção usando outros meios?
  • E : Podemos forçar e condições rror acontecer?
  • P : Você p características ESEMPENHO dentro de limites?

Pessoalmente, acho que você pode ir muito longe verificando se obteve os resultados corretos (1 + 1 deve retornar 2 em uma função de adição), experimentando todas as condições de contorno que você pode pensar (como usar dois números cuja soma é maior do que o valor máximo inteiro na função add) e forçando condições de erro, como falhas de rede.

Peter Evjan
fonte
6

Bons testes precisam ser sustentáveis.

Ainda não descobri como fazer isso em ambientes complexos.

Todos os livros didáticos começam a se desgrudar à medida que sua base de código começa a atingir centenas de 1000 ou milhões de linhas de código.

  • As interações da equipe explodem
  • número de casos de teste explodem
  • interações entre componentes explodem.
  • tempo para construir todos os testes de unidade torna-se uma parte significativa do tempo de construção
  • uma mudança na API pode chegar a centenas de casos de teste. Mesmo que a mudança do código de produção tenha sido fácil.
  • o número de eventos necessários para sequenciar os processos no estado correto aumenta, o que, por sua vez, aumenta o tempo de execução do teste.

Uma boa arquitetura pode controlar parte da explosão de interação, mas, inevitavelmente, conforme os sistemas se tornam mais complexos, o sistema de teste automatizado cresce com isso.

É aqui que você começa a lidar com compensações:

  • apenas teste a API externa, caso contrário, a refatoração interna resulta em retrabalho significativo do caso de teste.
  • a configuração e a desmontagem de cada teste ficam mais complicadas, pois um subsistema encapsulado retém mais estado.
  • a compilação noturna e a execução automatizada de testes chegam a horas.
  • tempos de compilação e execução aumentados significa que os designers não executam ou não executarão todos os testes
  • para reduzir os tempos de execução de teste, você considera os testes de sequenciamento para reduzir a configuração e a desmontagem

Você também precisa decidir:

onde você armazena casos de teste em sua base de código?

  • como você documenta seus casos de teste?
  • os acessórios de teste podem ser reutilizados para economizar a manutenção do caso de teste?
  • o que acontece quando a execução de um caso de teste noturno falha? Quem faz a triagem?
  • Como você mantém os objetos fictícios? Se você tiver 20 módulos, todos usando seu próprio sabor de uma API de log de simulação, alterando as ondas da API rapidamente. Não apenas os casos de teste mudam, mas os 20 objetos simulados mudam. Esses 20 módulos foram escritos ao longo de vários anos por muitas equipes diferentes. É um problema clássico de reutilização.
  • os indivíduos e suas equipes entendem o valor dos testes automatizados - eles simplesmente não gostam de como a outra equipe está fazendo isso. :-)

Eu poderia continuar para sempre, mas o que quero dizer é que:

Os testes precisam ser mantidos.

DanM
fonte
5

Abordei esses princípios há algum tempo neste artigo da MSDN Magazine, que acho importante para qualquer desenvolvedor ler.

A maneira como defino testes de unidade "bons" é se eles possuem as três propriedades a seguir:

  • Eles são legíveis (nomenclatura, afirmações, variáveis, comprimento, complexidade ..)
  • Eles são mantidos (sem lógica, não especificado em excesso, com base no estado, refatorado ..)
  • Eles são confiáveis ​​(teste a coisa certa, isolado, não testes de integração ..)
RoyOsherove
fonte
Roy, concordo de todo o coração. Essas coisas são muito mais importantes do que a cobertura de casos extremos.
Matt Hinze
digno de confiança - excelente ponto!
ratkok de
4
  • O Teste de Unidade apenas testa a API externa de sua Unidade, você não deve testar o comportamento interno.
  • Cada teste de um TestCase deve testar um (e apenas um) método dentro desta API.
    • Casos de teste adicionais devem ser incluídos para casos de falha.
  • Teste a cobertura de seus testes: Uma vez que uma unidade é testada, 100% das linhas dentro desta unidade devem ter sido executadas.
Alotor
fonte
2

Jay Fields tem muitos bons conselhos sobre como escrever testes de unidade e há um post onde ele resume os conselhos mais importantes . Lá você vai ler que deve pensar criticamente sobre o seu contexto e julgar se o conselho vale para você. Você obtém uma tonelada de respostas incríveis aqui, mas cabe a você decidir qual é a melhor para o seu contexto. Experimente-os e apenas refatorando se isso cheirar mal para você.

Atenciosamente

marcospereira
fonte
1

Nunca assuma que um método trivial de 2 linhas funcionará. Escrever um teste de unidade rápido é a única maneira de evitar que o teste nulo ausente, o sinal de menos mal colocado e / ou o erro de escopo sutil o mordam, inevitavelmente quando você tem ainda menos tempo para lidar com isso do que agora.

Joel in Gö
fonte
1

Eu apoio a resposta "A TRIP", exceto que testes DEVEM depender uns dos outros !!!

Por quê?

DRY - Dont Repeat Yourself - aplica-se a testes também! As dependências de teste podem ajudar a 1) economizar tempo de configuração, 2) economizar recursos de fixação e 3) apontar falhas. Claro, apenas considerando que sua estrutura de teste oferece suporte a dependências de primeira classe. Caso contrário, admito, eles são ruins.

Acompanhamento http://www.iam.unibe.ch/~scg/Research/JExample/

Akuhn
fonte
Eu concordo com você. TestNG é outra estrutura na qual as dependências são permitidas facilmente.
Davide,
0

Freqüentemente, os testes de unidade são baseados em objetos ou dados fictícios. Gosto de escrever três tipos de testes de unidade:

  • Testes de unidade "transitórios": eles criam seus próprios objetos / dados fictícios e testam sua função com eles, mas destroem tudo e não deixam rastros (como nenhum dado em um banco de dados de teste)
  • Teste de unidade "persistente": eles testam funções dentro de seu código, criando objetos / dados que serão necessários para funções mais avançadas posteriormente para seu próprio teste de unidade (evitando que essas funções avançadas recriem sempre seu próprio conjunto de objetos / dados fictícios)
  • Testes de unidade "baseados em persistência": testes de unidade usando objetos / dados fictícios que já existem (porque foram criados em outra sessão de teste de unidade) pelos testes de unidade persistentes.

O objetivo é evitar repetir tudo para poder testar todas as funções.

  • Eu executo o terceiro tipo com muita frequência porque todos os objetos / dados fictícios já estão lá.
  • Eu executo o segundo tipo sempre que meu modelo muda.
  • Eu executo o primeiro para verificar as funções básicas de vez em quando, para verificar as regressões básicas.
VonC
fonte
0

Pense sobre os 2 tipos de teste e trate-os de forma diferente - teste funcional e teste de desempenho.

Use diferentes entradas e métricas para cada um. Você pode precisar usar um software diferente para cada tipo de teste.

Techboy
fonte
Então, que tal o teste de unidade?
Spoike de
0

Eu uso uma convenção de nomenclatura de teste consistente descrita pelos padrões de nomenclatura de teste de unidade de Roy Osherove Cada método em uma determinada classe de caso de teste tem o seguinte estilo de nomenclatura MethodUnderTest_Scenario_ExpectedResult.

    A primeira seção de nome de teste é o nome do método no sistema em teste.
    A seguir está o cenário específico que está sendo testado.
    Finalmente estão os resultados desse cenário.

Cada seção usa Upper Camel Case e é delimitada por uma pontuação inferior.

Achei isso útil quando executo o teste, os testes são agrupados pelo nome do método em teste. E ter uma convenção permite que outros desenvolvedores entendam a intenção do teste.

Também acrescento parâmetros ao nome do Método se o método em teste estiver sobrecarregado.

Yack
fonte