Шаблоны для доступа к иерархии субъектов в 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 с помощью этого типа автоматического управления иерархией субъектов?

4 10

4 ответа:

В этом типе иерархической структуры я бы фактически отказался от использования 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
Но это может быть делом вкуса. Надеюсь, это поможет.

Я искал то же самое, и хотел использовать 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

Я нашел этот вопрос, пытаясь сделать что-то подобное. Мое решение можно найти в 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

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

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

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

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