Pergunta

Uma vez que eu comecei a usar rspec, eu tive um problema com a noção de acessórios. As minhas preocupações principais são o seguinte:

  1. I usar o teste para revelar um comportamento surpreendente. Eu nem sempre sou inteligente o suficiente para enumerar todos os casos borda possível para os exemplos que eu estou testando. Usando luminárias codificados parece limitar porque só testa o meu código com os casos muito específicos que eu imaginava. (Na verdade, a minha imaginação também está limitando em relação aos quais casos de teste I).

  2. I usar o teste como uma forma de documentação para o código. Se eu tiver codificado valores de fixação, é difícil para revelar o que um teste particular está tentando demonstrar. Por exemplo:

    describe Item do
      describe '#most_expensive' do
        it 'should return the most expensive item' do
          Item.most_expensive.price.should == 100
          # OR
          #Item.most_expensive.price.should == Item.find(:expensive).price
          # OR
          #Item.most_expensive.id.should == Item.find(:expensive).id
        end
      end
    end
    

    Usando o primeiro método dá ao leitor nenhuma indicação de que o item mais caro é, apenas que o seu preço é 100. Todos os três métodos peço ao leitor para levá-la na fé que o :expensive acessório é o mais caro listado na fixtures/items.yml. Um programador descuidado poderia quebrar testes através da criação de um Item em before(:all), ou através da inserção de um outro acessório em fixtures/items.yml. Se isso é um arquivo grande, pode levar um longo tempo para descobrir qual é o problema.

Uma coisa que eu comecei a fazer é adicionar um método #generate_random a todos os meus modelos. Este método só está disponível quando estou executando minhas especificações. Por exemplo:

class Item
  def self.generate_random(params={})
    Item.create(
      :name => params[:name] || String.generate_random,
      :price => params[:price] || rand(100)
    )
  end
end

(Os detalhes específicos de como eu fazer isso é na verdade um pouco mais limpa. Eu tenho uma classe que lida com a geração e limpeza de todos os modelos, mas este código é suficiente claro para o meu exemplo.) Assim, no exemplo acima, eu teste de força da seguinte forma. Um aviso para a simulação de coração: o meu código depende muito do uso de before(:all):

describe Item do
  describe '#most_expensive' do
    before(:all) do
      @items = []
      3.times { @items << Item.generate_random }
      @items << Item.generate_random({:price => 50})
    end

    it 'should return the most expensive item' do
      sorted = @items.sort { |a, b| b.price <=> a.price }
      expensive = Item.most_expensive
      expensive.should be(sorted[0])
      expensive.price.should >= 50      
    end
  end
end

Desta forma, meus testes revelam um melhor comportamento surpreendente. Quando eu gerar dados dessa maneira, eu ocasionalmente tropeçar em cima de um caso extremo em que meu código não se comporta como esperado, mas que eu não teria pego se eu estava usando apenas acessórios. Por exemplo, no caso de #most_expensive, se eu esqueci de lidar com o caso especial onde vários itens compartilhar o preço mais caro, meu teste ocasionalmente falhar no primeiro should. Vendo as falhas não-deterministas em Autospec Would pista me em que algo estava errado. Se eu estava usando apenas luminárias, pode demorar muito mais tempo para descobrir esse bug.

Os meus testes também fazer um trabalho um pouco melhor de demonstrar em código que o comportamento esperado é. Meu teste deixa claro que ordenados é uma matriz de itens classificados em ordem decrescente por preço. Desde que eu esperar #most_expensive ser igual ao primeiro elemento dessa matriz, é ainda mais óbvio o que o comportamento esperado de most_expensive é.

Assim, esta é uma má prática? É o meu medo de luminárias uma irracional? Está escrevendo um método generate_random para cada modelo muito trabalho? Ou isso funciona?

Foi útil?

Solução

Esta é uma resposta para o seu segundo ponto:

(2) I usar o teste como uma forma de documentação para o código. Se eu tiver valores de fixação codificados, é difícil para revelar o que um teste particular está tentando demonstrar.

Eu concordo. Idealmente exemplos de especificação deve ser compreensível por si mesmos. Usando luminárias é problemático, porque ele divide as pré-condições do exemplo de seus resultados esperados.

Devido a isso, muitos usuários RSpec pararam de usar luminárias completamente. Em vez disso, construir os objetos necessários no próprio exemplo spec.

describe Item, "#most_expensive" do
  it 'should return the most expensive item' do
    items = [
      Item.create!(:price => 100),
      Item.create!(:price => 50)
    ]

    Item.most_expensive.price.should == 100
  end
end

Se o seu final com lotes de código clichê para criação do objeto, você deve dar uma olhada em algumas das muitas bibliotecas fábrica objeto de teste, tais como factory_girl , Machinist , ou < a href = "http://replacefixtures.rubyforge.org/" rel = "nofollow noreferrer"> FixtureReplacement .

Outras dicas

Estou surpreso que ninguém neste tópico ou em um Jason Baker ligada à mencionado Monte Carlo Testing . Essa é a única vez que eu amplamente utilizado entradas de teste aleatórios. No entanto, foi muito importante para fazer a reproduzível teste, tendo uma semente constante para o gerador de números aleatórios para cada caso de teste.

Nós pensamos muito sobre isso em um projeto recente da mina. No final, estamos liquidados em dois pontos:

  • A repetibilidade de casos de teste é de suma importância. Se você deve escrever um teste aleatório, estar preparado para documentá-lo extensivamente, porque se / quando ele falhar, você precisa saber exatamente o porquê.
  • Usando aleatoriedade como uma muleta para os meios de cobertura de código que você não quer ter uma boa cobertura ou você não entende o suficiente domínio para saber o que constitui casos de teste representativas. Descobrir o que é verdade e corrigi-lo em conformidade.

Em suma, a aleatoriedade pode muitas vezes ser mais problemas do que vale a pena. Considerar cuidadosamente se você estiver indo para usá-lo corretamente antes de puxar o gatilho. Nós finalmente decidiu que os casos de testes aleatórios eram uma má idéia em geral, e para ser usado com moderação, se em tudo.

Muita informação boa já foi publicado, mas veja também: Fuzz Testing . A palavra na rua é que a Microsoft usa essa abordagem em um monte de seus projetos.

A minha experiência com o teste é principalmente com programas simples escritos em C / Python / Java, então eu não tenho certeza se isso é inteiramente aplicável, mas sempre que tenho um programa que pode aceitar qualquer tipo de entrada do usuário, eu sempre incluir um teste com dados de entrada aleatória, ou pelo menos dados de entrada gerados pelo computador de forma imprevisível, porque você nunca pode fazer suposições sobre o que os usuários vão entrar. Ou, bem, você pode , mas se o fizer, então algum hacker que não fazer essa suposição pode muito bem encontrar um bug que você totalmente esquecido. entrada gerado-máquina é o melhor (único?) maneira que eu conheço para manter viés humano completamente fora dos procedimentos de teste. Claro, a fim de reproduzir um teste falhado você tem que fazer algo como salvar a entrada de teste para um arquivo ou imprimi-lo (se é texto) antes de executar o teste.

testes aleatórios é uma má prática de um tempo que você não tem uma solução para o problema oráculo , ou seja, determinar qual é o resultado esperado de seu software dada a sua entrada.

Se você resolveu o problema Oracle, você pode obter um passo além do que a geração de entrada aleatória simples. Você pode escolher distribuições de entrada tal que partes específicas do seu software se exercitam mais do que com aleatória simples.

Você, então, mudar de testes aleatórios para testes estatísticos.

if (a > 0)
    // Do Foo
else (if b < 0)
    // Do Bar
else
    // Do Foobar

Se você selecionar a e b aleatoriamente na faixa int, você exercita Foo 50% do tempo, Bar 25% do tempo e Foobar 25% do tempo. É provável que você vai encontrar mais bugs no Foo do que em Bar ou Foobar.

Se você selecionar a tal que é negativo 66,66% do tempo, Bar e Foobar get exercido mais do que com a sua primeira distribuição. Na verdade, os três ramos se exercido cada 33,33% do tempo.

É claro que, se o resultado observado é diferente do que o seu resultado esperado, você tem que registrar tudo que pode ser útil para reproduzir o bug.

Gostaria de sugerir ter um olhar para Machinist:

http://github.com/notahat/machinist/tree/master

Machinist irá gerar dados para você, mas é repetível, de modo que cada test-run tem o mesmo dados aleatórios.

Você poderia fazer algo semelhante por semear o gerador de números aleatórios de forma consistente.

Um problema com casos de teste gerados aleatoriamente é que validar a resposta deve ser calculado por código e você não pode ter certeza que não tem erros:)

Você também pode ver este tópico: Testing com entradas aleatórias melhores práticas .

A eficácia de tais testes em grande parte depende da qualidade de gerador de números aleatórios que você usa e de como correta é o código que traduz a saída do RNG em dados de teste.

Se o RNG nunca produz valores causando o seu código para entrar em alguma condição caso borda que você não vai ter neste caso coberto. Se o código que traduz a saída do RNG para entrada do código de teste é defeituoso, pode acontecer que, mesmo com um bom gerador de você ainda não atingiu todos os casos de ponta.

Como é que vai testar para isso?

O problema com a aleatoriedade em casos de teste é que a saída é, bem, aleatória.

A idéia por trás testes (especialmente testes de regressão) é verificar que nada está quebrado.

Se você encontrar algo que está quebrado, você precisa incluir esse teste cada vez que a partir de então, caso contrário, você não vai ter um conjunto consistente de testes. Além disso, se você executar um teste aleatório que obras, então você precisa incluir esse teste, porque a sua possível que você pode quebrar o código para que o teste falhar.

Em outras palavras, se você tem um teste que usa dados aleatórios gerados na mosca, eu acho que isso é uma má idéia. Se, no entanto, você pode usar um conjunto de dados aleatórios, que você então armazenar e reutilizar, esta pode ser uma boa idéia. Isto poderia tomar a forma de um conjunto de sementes para um gerador de números aleatórios.

Este armazenamento dos dados gerados permite que você encontre a resposta 'correta' a esses dados.

Então, eu recomendo o uso de dados aleatórios para explorar o seu sistema, mas usar os dados definidos em seus testes (que pode ter sido originalmente gerada aleatoriamente de dados)

O uso de dados de teste aleatório é uma excelente prática -. Os dados do teste codificados testa apenas os casos, você explicitamente pensado, enquanto que dados aleatórios libera fora suas suposições implícitas que pode estar errado

Eu recomendo usar Menina e ffaker Fábrica para isso. (Nunca use Rails luminárias para qualquer coisa em qualquer circunstância.)

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