[lang_en]Dynamic Specifications with Rspec[/lang_en][lang_ru]Динамические спецификации в Rspec[/lang_ru]
11 May2007

[lang_en]

Sometimes you may need to create a set of rspec specifications with pretty similar structure and small differences. I’ve got such situation in my project and decided to try to use Ruby’s dynamic code generation features to make my spec file shorter.

I have some multiplexing helper in my templates which allows me to use the same template for different similar pages. This helper returns URL from the set of params and a type. It could accept 5 different url types and raises an Exception when requested URL type is invalid. Without this dynamic code generation feature I would need to create 5 different specifications (one for each URL type) to be able to see each URL type test as a separate line in test results log. But with this simple technique my code looks like following now:

[/lang_en]

[lang_ru]

Иногда бывают моменты, когда Вам может быть нужно создать набор спецификаций для rspec, отличающихся одним-двумя вызовами или параметрами. У меня в проекте сложилась такая ситуация, и я решил попробовать использовать возможности Ruby для динамической генерации кода чтобы, сделать spec-файлы короче и избежать дублирования.

У меня есть хелпер, который используется в нескольких универсальных темплейтах для генерации похожих страниц. Этот хелпер возвращвет URL по набору параметров и типу ссылки. Он может принимать 5 различных типов ссылок и выбрасывает исключение с случаях, когда тип ссылки не поддерживается. Без использования динамической генерации кода мне пришлось бы создать 5 различных спецификаций (по одной для каждого типа ссылок) для того, чтобы иметь возможность видеть каждый тип ссылок отдельной строкой в результатах теста. С использованием же динамической генерации код выглядит примерно так:

[/lang_ru]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
describe VideoHelper, 'when profile_video_url method called' do
  before do
    @user = mock('user')
  end
 
  url_types = {
    'personal_feed' => 'personal_feed',
    'favorites' => 'favorites',
    'voted' => 'voted_videos',
    'posted' => 'posted_videos',
    'commented' => 'commented_videos'
  }
 
  url_types.each do |url_type, route|
    it "should return #{route}_url for #{url_type} type urls" do
      @user.should_receive(:login).at_least(1).times.and_return('login')
      profile_video_url(url_type, @user, 2, 'expert').should == send("#{route}_url", @user, 2, 'expert')
    end
  end
 
  it 'should raise ArgumentError("Invalid feed type") on invalid url_types' do
    lambda { profile_video_url('crap', @user, 2, 'expert') }.should raise_error(ArgumentError, 'Invalid feed type')
  end
end

[lang_en]

This technique could be used even to create entire describe sections, but I would not like to show tons of code here. Anyways, the idea is pretty simple: you could use some loop with nested describe section and send() method calls to dynamically construct your code.

[/lang_en]

[lang_ru]

Эта техника может быть использована даже для целых describe-секций, но я не буду приводить здесь гору кода, который получился у меня. Так или иначе, идея предельно проста: вы можете использовать любые циклы с секцией describe внутри и вызовами метода send() для динамического конструирования Вашего кода.

[/lang_ru]