Подтвердить что ты не робот

Как объявить переменную, разделяемую между примерами в RSpec?

Предположим, что у меня есть следующая спецификация:

...
describe Thing do

  it 'can read data' do
     @data = get_data_from_file  # [ '42', '36' ]
     expect(@data.count).to eq 2
  end

  it 'can process data' do
     expect(@data[0].to_i).to eq 42  # Fails because @data is nil
  end

end
...

Все, что я хотел, это иметь переменную, совместно используемую в данном описании или контексте. Я бы написал значение в одном примере и прочитал его в другом. Как это сделать?

4b9b3361

Ответ 1

Вы должны использовать блок before(:each) или before(:all):

describe Thing do
  before(:each) do
    @data = get_data_from_file  # [ '42', '36' ]
  end

  it 'can read data' do
    expect(@data.count).to eq 2
  end

  it 'can process data' do
    expect(@data[0].to_i).to eq 42
  end
end

Разница в том, что before(:each) будет выполняться для каждого случая отдельно и before(:all) один раз перед всеми примерами в этом describe/context. Я бы рекомендовал вам выбрать before(:each) over before(:all), потому что каждый пример будет изолирован в этом случае, что является хорошей практикой.

Редкие случаи, когда вы хотите использовать before(:all), например, если ваш get_data_from_file имеет длительное время выполнения, в этом случае вы, конечно же, можете жертвовать изоляцией тестов в пользу скорости. Но я хочу знать, что при использовании before(:all) изменение вашей переменной @data в одном тесте (блок it) приведет к неожиданным последствиям для других тестов в области describe/context, потому что они будут делиться им.

before(:all) пример:

describe MyClass do
  before(:all) do
    @a = []
  end

  it { @a << 1; p @a }
  it { @a << 2; p @a }
  it { @a << 3; p @a }
end

Будет выводиться:

[1]
[1, 2]
[1, 2, 3]

ОБНОВЛЕНО

Чтобы ответить на вопрос

describe MyClass do
  before(:all) do
    @a = []
  end

  it { @a = [1]; p @a }
  it { p @a }
end

Выведет

[1]
[]

Поскольку в первом it вы локально назначаете переменную-экземпляр @a, поэтому она не такая же, как в блоке @a in before(:all) и не видна для других блоков it, вы можете проверить ее, вывод object_id s. Таким образом, только модификация будет делать трюк, назначение приведет к созданию нового объекта.

Итак, если вы назначаете переменную несколько раз, вы, вероятно, должны получить один блок it и несколько ожиданий в нем. Это приемлемо, согласно наилучшим методам.

Ответ 2

Это действительно цель RSpec позволяет помощнику, который позволяет вам делать это с помощью вашего кода:

...
describe Thing do
  let(:data) { get_data_from_file }

  it 'can read data' do
     expect(data.count).to eq 2
  end

  it 'can process data' do
     expect(data[0].to_i).to eq 42
  end

end
...

Ответ 3

Я столкнулся с этой проблемой. Как я решил это, используя драгоценный камень factory_girl.

Вот основные сведения:

создайте factory (здесь фрагмент кода:

require 'factory_girl'
require 'faker' # you can use faker, if you want to use the factory to generate fake data

FactoryGirl.define do
  factory :generate_data, class: MyModule::MyClass do
    key 'value'
  end
end

Теперь после создания factory вам нужно создать модель, которая выглядит так:

Module MyModule
  class MyClass
    attr_accessor :key

    #you can also place methods here to call from your spec test, if you wish
    # def self.test
        #some test
    # end
  end
end

Теперь, возвращаясь к вашему примеру, вы можете сделать что-то вроде этого:

describe Thing do
  before(:all) do
  @data = FactoryGirl.build(:generate_data)
  end

  it 'can read data' do
     @data.key = get_data_from_file  # [ '42', '36' ]
     expect(@data.key.count).to eq 2
  end

  it 'can process data' do
     expect(@data.key[0].to_i).to eq 42  # @data will not be nil. at this point. whatever @data.key is equal to last which was set in your previous context will be what data.key is here
  end

end

В любом случае, удачи дайте нам знать, если у вас есть другое решение!