Поскольку я начал использовать rspec, у меня возникла проблема с понятием приборов. Мои основные проблемы следующие:
Я использую тестирование, чтобы выявить удивительное поведение. Я не всегда достаточно умен, чтобы перечислять все возможные варианты для примеров, которые я тестирую. Использование жестко закодированных приборов кажется ограничивающим, потому что это тестирует мой код только с очень конкретными случаями, которые я вообразил. (По общему признанию, мое воображение также ограничивает относительно того, какие случаи я проверяю.)
Я использую тестирование как форму документации для кода. Если у меня есть жестко запрограммированные значения приборов, трудно выявить, что пытается продемонстрировать конкретный тест. Например:
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
Использование первого метода не дает читателю никаких указаний на то, что является самым дорогим предметом, только то, что его цена составляет 100. Все три метода просят читателя поверить, что прибор :expensive
является самым дорогим из перечисленных в fixtures/items.yml
. Неосторожный программист может прервать тесты, создав Item
в before(:all)
или вставив другое устройство в fixtures/items.yml
. Если это большой файл, может потребоваться много времени, чтобы выяснить, в чем проблема.
Одна вещь, которую я начал делать, это добавить метод #generate_random
ко всем моим моделям. Этот метод доступен только когда я запускаю свои спецификации. Например:
class Item
def self.generate_random(params={})
Item.create(
:name => params[:name] || String.generate_random,
:price => params[:price] || rand(100)
)
end
end
(Конкретные детали того, как я это делаю, на самом деле немного чище. У меня есть класс, который обрабатывает генерацию и очистку всех моделей, но этот код достаточно ясен для моего примера.) Так что в приведенном выше примере я может проверить следующим образом. Предупреждение для финта сердца: мой код сильно зависит от использования 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
Таким образом, мои тесты лучше показывают удивительное поведение. Когда я генерирую данные таким образом, я иногда натыкаюсь на крайний случай, когда мой код работает не так, как ожидалось, но который я бы не уловил, если бы использовал только приборы. Например, в случае #most_expensive
, если я забуду обработать особый случай, когда несколько предметов имеют самую высокую цену, мой тест иногда будет проваливаться при первом should
. Видя недетерминированные сбои в AutoSpec, я понял бы, что что-то не так. Если бы я использовал только приборы, обнаружение такой ошибки могло бы занять гораздо больше времени.
Мои тесты также немного лучше демонстрируют в коде ожидаемое поведение. Мой тест показывает, что отсортированный - это массив элементов, отсортированных в порядке убывания по цене. Поскольку я ожидаю, что #most_expensive
будет равен первому элементу этого массива, еще более очевидно, каково ожидаемое поведение most_expensive
.
Так это плохая практика? Является ли мой страх перед приспособлениями иррациональным? Написание generate_random
метода для каждой модели - это слишком много работы? Или это работает?