Шаблоны для доступа к иерархии объектов в RSpec

При использовании RSpec для проверки глубоко вложенных структур данных я нахожу необходимость определять темы в вложенных контекстах в терминах субъектов в контекстах содержания. Я широко рассмотрел, но не нашел примеров того, как обойтись без определения многих переменных. Это усложняет спецификации и ограничивает возможность повторного использования спецификации. Мне любопытно, есть ли способ сделать это в RSpec в том виде, в каком он стоит, и если нет, то что бы было хорошим способом подойти к проблеме.

Сейчас мой код выглядит примерно так:

context 'with a result which is a Hash' do
  before do
    @result = get_result()
  end
  subject { @result }
  it { should be_a Hash }
  context 'with an Array' do
    before do
      @array_elem = @result[special_key]
    end
    subject { @array_elem }
    it { should be_an Array }
    context 'that contains a Hash' do
      before do
        @nested_hash = ...
      end
      subject { @nested_hash }
      ...
    end
  end
end

Вместо этого я лучше напишу что-нибудь по строкам:

context 'with a result which is a Hash' do
  subject { get_result }
  it { should be_a Hash }
  context 'with an Array' do
    subject { parent_subject[special_key] }
    it { should be_an Array }
    context 'that contains a Hash' do
      subject { do_something_with(parent_subject) }
      ...
    end
  end
end

Какой способ расширить RSpec с помощью этого типа автоматического управления иерархией объектов?

Ответ 1

В этом типе иерархической структуры я бы фактически отказался от использования subject и сделал объект явным. Хотя это может привести к еще большему набору текста (если у вас много тестов), это также понятно. Вложенные subject -стояния могут также путать то, что на самом деле тестируется, если вы на три уровня вниз.

  context 'with a result which is a Hash' do
    before do
      @result = get_result()
    end
    it { @result.should be_a Hash }
    context 'special_key' do
      before do
        @array_elem = @result[special_key]
      end
      it { @array_elem.should be_an Array }
      context 'that contains a Hash' do
        before do
          @nested_hash = ...
        end
        it { @nested_hash.should be_a Hash }
        ...
      end
    end
  end

Но это может быть вопросом вкуса. Надеюсь, это поможет.

Ответ 2

Я искал одно и то же, и хотел использовать subject, чтобы использовать its, поэтому я реализовал его следующим образом:

describe "#results" do
  let(:results) { Class.results }

  context "at the top level" do
    subject { results }

    it { should be_a Hash }
    its(['featuredDate']) { should == expected_date }
    its(['childItems']) { should be_a Array }
  end

  context "the first child item" do
    subject { results['childItems'][0] }

    it { should be_a Hash }
    its(['title']) { should == 'Title' }
    its(['body']) { should == 'Body' }
  end

  context "the programme info for the first child item" do
    subject { results['childItems'][0]['programme'] }

    it { should be_a Hash }
    its(['title']) { should == 'title' }
    its(['description']) { should == 'description' }
  end
end

Ответ 3

Я нашел этот вопрос, пытаясь сделать что-то подобное. Мое решение можно найти на https://gist.github.com/asmand/d1ccbcd01789353c01c3

То, что он делает, - это проверить расчет времени работы в течение недели за Рождество, то есть на данной неделе, только понедельник и пятница - рабочие дни, остальные - праздники.

Решение основано на названных предметах. То есть.

describe WeeklyFlexCalculator, "during Christmas week" do

  subject(:calculation) { WeeklyFlexCalculator.new(params).calculate }

  context "with no work performed" do
    it { should have(1).item }

    context "the week calculated" do
      subject(:workweek) {calculation[0]}

      its([:weekTarget]) { should eq 15.0 }
      its([:weekEffort]) { should eq 0.0 }

      context "the work efforts" do
        subject(:efforts) {workweek[:efforts]}

        it { should have(2).items }

        context "the first work effort" do
          subject(:effort) {efforts[0]}

          its([:target]) {should eq 7.5}
          its([:diff]) {should eq -7.5}
          its([:effort]) {should eq 0.0}
        end
      end
    end
  end
end

Много кода для краткости осталось, но полный пример можно найти в связанном сущности.

Ответ 4

Я предполагаю, что эта функциональность не встроена в Rspec, потому что она будет стимулировать более сложные спецификации и код. Согласно передовым методам ООП, классы должны нести единую ответственность. В результате ваши спецификации должны быть краткими и понятными. Для краткости и удобочитаемости спецификация должна иметь только один тип предмета. Могут быть вариации на эту тему, но в конечном итоге все они должны основываться на классе/объекте, который вы описываете.

Если бы я был вами, я бы сделал шаг назад и действительно спросил себя, что я действительно пытаюсь сделать. Это похоже на запах кода, если вы обнаруживаете, что у вас есть несколько предметов разного класса в одной и той же спецификации. Это либо проблема с тем, как вы используете Rspec, либо ваш класс делает слишком много. Другое дело, что вы должны тестировать поведение своих объектов, а не детали того, что они делают внутри. Кто заботится, если что-то является массивом или хешем, если он ведет себя так, как должен?

Возьмите aways... Если ваш ребенок действительно будет отдельным классом со своей собственной спецификацией? Вы испытываете тестирование, а не поведение?