Desde que comencé a usar rspec, tuve un problema con la noción de accesorios. Mi principal preocupación es la siguiente:¿Es una mala práctica generar aleatoriamente datos de prueba?
Uso las pruebas para revelar un comportamiento sorprendente. No siempre soy lo suficientemente inteligente como para enumerar todos los casos posibles para los ejemplos que estoy probando. Usar accesorios codificados parece limitado porque solo prueba mi código con los casos muy específicos que he imaginado. (Es cierto que mi imaginación también es limitante con respecto a los casos que pruebo).
Utilizo las pruebas como una forma de documentación para el código. Si tengo valores de dispositivo codificados, es difícil revelar lo que una prueba en particular está tratando de demostrar. Por ejemplo:
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 el primer método proporciona al lector ninguna indicación de lo que el artículo más caro es, solo que su precio es de 100. Los tres métodos piden al lector a tomar en la fe de que el aparato es el
:expensive
el más caro enumerado enfixtures/items.yml
. Un programador descuidado podría romper las pruebas creando unItem
enbefore(:all)
, o insertando otro dispositivo enfixtures/items.yml
. Si ese es un archivo grande, podría llevar mucho tiempo descubrir cuál es el problema.
Una cosa que he empezado a hacer es añadir un método #generate_random
a todos mis modelos. Este método solo está disponible cuando estoy ejecutando mis especificaciones. Por ejemplo:
class Item
def self.generate_random(params={})
Item.create(
:name => params[:name] || String.generate_random,
:price => params[:price] || rand(100)
)
end
end
(. Los detalles específicos de cómo hacer esto en realidad son un poco más limpio que tengo una clase que se encarga de la generación y la limpieza de todos los modelos, pero este código es lo suficientemente claro para mi ejemplo.) Entonces, en el ejemplo anterior, podría probar lo siguiente. Una advertencia para los débiles de corazón: mi código se basa principalmente en el 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
De esta manera, mis pruebas revelan un mejor comportamiento sorprendente. Cuando genero datos de esta manera, de vez en cuando tropiezo con un caso marginal en el que mi código no se comporta como se esperaba, pero que no habría captado si solo estuviera usando dispositivos. Por ejemplo, en el caso de #most_expensive
, si olvidé manejar el caso especial donde varios artículos comparten el precio más caro, mi prueba ocasionalmente fallaría en el primer should
. Ver las fallas no deterministas en AutoSpec me daría una pista de que algo andaba mal. Si solo estuviera usando accesorios, podría llevar mucho más tiempo descubrir tal error.
Mis pruebas también hacen un trabajo un poco mejor al demostrar en código cuál es el comportamiento esperado. Mi prueba deja en claro que ordenado es una matriz de elementos ordenados en orden descendente por precio. Como espero que #most_expensive
sea igual al primer elemento de esa matriz, es aún más obvio cuál es el comportamiento esperado de most_expensive
.
Entonces, ¿es esta una mala práctica? ¿Mi miedo a los accesorios es irracional? Está escribiendo demasiado un método generate_random
para cada modelo? ¿O esto funciona?
La línea "3.times {@items 50})" no se ve bien. –
Y ahora, apenas 58 meses después, respondo ... No se ve bien porque tiene "< <" en él ... pero no se escapó correctamente. – bobocopy