Da quando ho iniziato a utilizzare rspec, ho riscontrato un problema con la nozione di proiettori. Le mie preoccupazioni principali sono le seguenti:È una cattiva pratica generare in modo casuale dati di test?
Uso il test per rivelare un comportamento sorprendente. Non sono sempre abbastanza intelligente da elencare ogni possibile caso limite per gli esempi che sto testando. L'uso di dispositivi fissi sembra limitante perché verifica solo il mio codice con i casi molto specifici che ho immaginato. (Certo, la mia immaginazione è anche limitante rispetto a quali casi provo.)
Uso il test come una forma di documentazione per il codice. Se ho valori di fixture hard-coded, è difficile rivelare cosa prova un test specifico. Per esempio:
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
Utilizzando il primo metodo dà al lettore alcuna indicazione quanto l'elemento più costoso è, solo che il suo prezzo è di 100. Tutti e tre i metodi di chiedere al lettore di prendere sul fede che l'apparecchio è il
:expensive
quello più costoso elencato infixtures/items.yml
. Un programmatore disattento potrebbe interrompere i test creandoItem
inbefore(:all)
o inserendo un altro dispositivo infixtures/items.yml
. Se questo è un file di grandi dimensioni, potrebbe essere necessario molto tempo per capire qual è il problema.
Una cosa che ho iniziato a fare è aggiungere un metodo #generate_random
a tutti i miei modelli. Questo metodo è disponibile solo quando eseguo le mie specifiche. Per esempio:
class Item
def self.generate_random(params={})
Item.create(
:name => params[:name] || String.generate_random,
:price => params[:price] || rand(100)
)
end
end
(. I dettagli specifici di come faccio questo sono in realtà un po 'più pulito ho una classe che gestisce la generazione e la rimozione di tutti i modelli, ma questo codice è abbastanza chiaro per il mio esempio.) Quindi nell'esempio sopra, potrei provare come segue. Un avvertimento per la finta di cuore: il mio codice si basa pesantemente su uso di 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
In questo modo, il mio test migliore rivelano un comportamento sorprendente. Quando genero i dati in questo modo, occasionalmente mi imbatto in un caso limite in cui il mio codice non si comporta come previsto, ma che non avrei catturato se stavo usando solo fixture. Ad esempio, nel caso di #most_expensive
, se ho dimenticato di gestire il caso speciale in cui più articoli condividono il prezzo più costoso, il mio test a volte fallisce al primo should
. Vedere i fallimenti non deterministici in AutoSpec mi indurrebbe a capire che qualcosa non andava. Se stavo usando solo le fixture, potrebbe volerci molto più tempo per scoprire un simile bug.
I miei test fanno anche un lavoro leggermente migliore di dimostrare in codice quale sia il comportamento previsto. Il mio test chiarisce che ordinati è una serie di elementi ordinati in ordine decrescente per prezzo. Poiché mi aspetto che #most_expensive
sia uguale al primo elemento di quell'array, è ancora più ovvio quale sia il comportamento previsto di most_expensive
.
Quindi, questa è una cattiva pratica? La mia paura dei proiettori è irrazionale? La scrittura di un metodo generate_random
per ciascun modello funziona troppo? O funziona?
La riga "3.times {@items 50})" non sembra corretta. –
E ora, solo 58 mesi dopo, rispondo ... Non sembra giusto perché ha "< <" in esso ... ma non è correttamente scappato. – bobocopy