Pergunta

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

Minha pergunta é: você segue alguma regra de conduta na redação de testes para evitar problemas no futuro?Para ser mais específico:O que são as propriedades de bons testes unitários ou como você escreve seus testes?

Sugestões independentes de idioma são incentivadas.

Foi útil?

Solução

Deixe-me começar conectando fontes - Teste de unidade pragmático em Java com JUnit (Há uma versão com C#-Nunit também..mas eu tenho esse..é agnóstico em sua maior parte.Recomendado.)

Bons testes devem ser UMA VIAGEM (A sigla não é suficientemente pegajosa - tenho uma impressão da folha de dicas do livro que tive que retirar para ter certeza de que entendi direito.)

  • Automático :A invocação de testes, bem como a verificação de resultados para APROVAÇÃO/REPROVAÇÃO devem ser automáticas
  • Minucioso:Cobertura;Embora os bugs tendam a se agrupar em torno de certas regiões do código, certifique-se de testar todos os principais caminhos e cenários.Use ferramentas se precisar conhecer regiões não testadas
  • Repetivel:Os testes devem produzir os mesmos resultados todas as vezes.toda vez.Os testes não devem depender de parâmetros incontroláveis.
  • Independente:Muito importante.
    • Os testes devem teste apenas uma coisa de uma vez.Múltiplas asserções são aceitáveis, desde que todas testem um recurso/comportamento.Quando um teste falha, ele deve identificar a localização do problema.
    • Testes não devem confiar um no outro - Isolado.Nenhuma suposição sobre a ordem de execução do teste.Garanta uma 'lousa limpa' antes de cada teste usando configuração/desmontagem apropriadamente
  • Profissional:No longo prazo, você terá tanto código de teste quanto de produção (se não mais), portanto siga o mesmo padrão de bom design para seu código de teste.Classes de métodos bem fatoradas com nomes que revelam intenções, sem duplicação, testes com bons nomes, etc.

  • Bons testes também são executados Rápido.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 frequentemente 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 :Isso pode ser considerado parte do Profissional - mas nunca é demais enfatizar.Um teste decisivo seria encontrar alguém que não faça parte de sua equipe e pedir-lhe que descubra o comportamento em teste em alguns minutos.Os testes precisam ser mantidos exatamente como 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 exemploo estilo TestDox).Evite sobrecarregar o teste com "detalhes incidentais".torne-se um minimalista.

Além destas, a maioria das outras são diretrizes que reduzem o trabalho de baixo benefício:por exemplo.'Não teste código que não lhe pertence' (por exemploDLLs de terceiros).Não teste getters e setters.Fique de olho na relação custo-benefício ou na probabilidade de defeito.

Outras dicas

  1. Não escreva testes gigantescos. Como sugere a 'unidade' em 'teste unitário', faça cada um como atômico e isolado que possível.Se necessário, crie pré-condições usando objetos simulados, em vez de recriar manualmente muito do ambiente de usuário típico.
  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 usar uma ferramenta de cobertura de código como o NCover para ajudar a descobrir casos extremos que você ainda não testou.
  4. Tente escrever o teste antes a implementação. Pense no teste mais como uma especificação à qual sua implementação irá aderir.Cf.também desenvolvimento orientado a comportamento, um ramo mais específico do desenvolvimento orientado a testes.
  5. Ser consistente. Se você escrever testes apenas para parte do seu código, dificilmente será útil.Se você trabalha em equipe e alguns ou todos os outros não escrevem testes, isso também não é muito útil.Convença a si mesmo e a todos da importância (e economia de tempo propriedades) de teste, ou não se preocupe.

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

As 5 leis de testes de 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 Organizar/Agir/Afirmar.

  • Embora essa estratégia organizacional já exista há algum tempo e tenha chamado muitas coisas, a introdução do sigla "AAA" recentemente foi uma ótima maneira de transmitir isso.Tornar todos os seus testes consistentes com o estilo AAA facilita a leitura e a manutenção.

3.Sempre forneça uma mensagem de falha com suas afirmações.

Assert.That(x == 2 && y == 2, "An incorrect number of begin/end element 
processing events was raised by the XElementSerializer");
  • Uma prática simples, mas gratificante, que torna óbvio em seu aplicativo de execução o que falhou.Se você não fornecer uma mensagem, geralmente receberá algo como "Esperado verdadeiro, era falso" na saída de falha, o que faz com que você tenha que ler o teste para descobrir o que há de errado.

4.Comente o motivo do teste – qual é a suposição de negócios?

  /// 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, 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 fora.

5.Todo teste deve sempre reverter o estado de qualquer recurso que toca

  • Use maquetes sempre que possível para evitar lidar com recursos reais.
  • A limpeza deve ser feita no nível do teste.Os testes não devem confiar na ordem de execução.

Tenha 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 ao seu redor

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 esqueça que você também pode fazer testes de integração com sua estrutura xUnit mas mantenha os testes de integração e os testes de unidade separados

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 sua integração ou testes funcionais.

Algumas propriedades de ótimos testes unitários:

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

  • Quando você refatora, 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 unitários devem ser bem fatorados, assim como seu código de produção.

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


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

Eu não adoto essa abordagem.Em vez disso, eu 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...
    }
}

O que você procura é o delineamento dos comportamentos da classe em teste.

  1. Verificação de 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 dentro da classe.

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

Isso é especialmente útil ao refatorar seu código.Martin Fowler tem um interessante artigo sobre os testes em seu site.

HTH.

saúde,

Roubar

O teste deveria 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.

Eu gosto da sigla Right BICEP do mencionado acima Teste de unidade pragmático livro:

  • Certo:Os resultados são certo?
  • B:São todos os bcondições básicas corretas?
  • EU:Podemos verificar eurelacionamentos inversos?
  • C:Nós podemos cverificar os resultados usando outros meios?
  • E:Podemos forçar econdições de erro aconteceriam?
  • P:São pcaracterísticas de desempenho dentro dos limites?

Pessoalmente, sinto 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 puder imaginar (como usar dois números cuja soma é maior que o valor máximo do número inteiro na função add) e forçando condições de erro, como falhas de rede.

Bons testes precisam ser sustentáveis.

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

Todos os livros didáticos começam a não passar quando sua base de código começa a chegar às centenas de 1000 ou milhões de linhas de código.

  • As interações da equipe explodem
  • número de casos de teste explode
  • as interações entre os componentes explodem.
  • o tempo para construir todos os unittests se torna uma parte significativa do tempo de construção
  • uma alteração na API pode afetar 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 processos no estado correto aumenta, o que por sua vez aumenta o tempo de execução do teste.

A boa arquitetura pode controlar parte da explosão de interação, mas inevitavelmente à medida que os sistemas se tornam mais complexos, o sistema de teste automatizado cresce com ele.

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

  • teste apenas a API externa, caso contrário, a refatoração interna resultará em um retrabalho significativo dos casos de teste.
  • a configuração e a desmontagem de cada teste ficam mais complicadas à medida que um subsistema encapsulado retém mais estado.
  • a compilação noturna e a execução automatizada de testes chegam a horas.
  • aumento do tempo de compilação e execução significa que os designers não executarão ou não executarão todos os testes
  • para reduzir os tempos de execução de testes, considere sequenciar os testes 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 dos casos 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 simulados?Se você tiver 20 módulos, todos usando seu próprio tipo de API de registro simulado, a alteração das ondulações da API ocorrerá rapidamente.Não apenas os casos de teste mudam, mas também os 20 objetos simulados.Esses 20 módulos foram escritos ao longo de vários anos por muitas equipes diferentes.É um problema clássico de reutilização.
  • indivíduos e suas equipes entendem o valor dos testes automatizados, mas não gostam da maneira como a outra equipe os faz.:-)

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

Os testes precisam ser sustentáveis.

Abordei esses princípios há algum tempo Este artigo da revista MSDN o que considero importante para qualquer desenvolvedor ler.

A forma como defino testes de unidade "bons" é se eles possuírem as três propriedades a seguir:

  • Eles são legíveis (nomeação, afirmações, variáveis, comprimento, complexidade..)
  • Eles são sustentáveis ​​(sem lógica, não especificados demais, baseados em estado, refatorados...)
  • Eles são confiáveis ​​(teste a coisa certa, isolada, não testes de integração..)
  • O teste de unidade apenas testa a API externa da 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 testada uma unidade, 100% das linhas dentro desta unidade deveriam ter sido executadas.

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

Atenciosamente

Nunca presuma 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 sutil de escopo o afetem, inevitavelmente quando você tem ainda menos tempo para lidar com isso do que agora.

Eu apoio a resposta "A TRIP", exceto que os testes DEVEM confiar uns nos outros!!!

Por que?

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

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

Freqüentemente, os testes de unidade são baseados em objetos simulados ou dados simulados.Gosto de escrever três tipos de testes unitários:

  • Testes de unidade "transitórios":eles criam seus próprios objetos/dados simulados 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 do 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 simulados)
  • Testes de unidade "baseados em persistente":testes de unidade usando objetos/dados simulados que já estão lá (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 simulados 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.

Pense nos 2 tipos de testes e trate-os de forma diferente – testes funcionais e testes de desempenho.

Use entradas e métricas diferentes para cada um.Pode ser necessário usar software diferente para cada tipo de teste.

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

    A primeira seção do nome do 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.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top