Устранение жесткой вложенности в тестах RSpec с помощью процедур Ruby

Недавно я тестировал класс сервиса в Ruby, и мне нужно было убедиться, что определенные комбинации условий приводят к желаемым эффектам.

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

context 'when A1' do
  let(:a1) {}

  it 'R1' do
    # ...
  end

  context 'and B1' do
    let(:b1) {}

    it 'R1' do
      # ...
    end
  end

  context 'and B2' do
    let(:b2) {}

    it 'R1' do
      # ...
    end
  end
end

context 'when A2' do
  let(:a2) {}

  it 'R2' do
    # ...
  end

  context 'and B1' do
    let(:b1) {}

    it 'R3' do
      # ...
    end
  end

  context 'and B2' do
    let(:b2) {}

    it 'R2' do
      # ...
    end
  end
end
Войти в полноэкранный режим Выход из полноэкранного режима

В моем случае был еще один уровень вложенности, поэтому код был действительно нечитабельным и трудным для понимания. Здесь я удалил один уровень и очистил содержимое контекста, чтобы сохранить ваши глаза, дорогой читатель 🙂

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

shared_context 'A1' do
  let(:a1) {}
end

shared_context 'A2' do
  let(:a2) {}
end

shared_context 'B1' do
  let(:b1) {}
end

shared_context 'B2' do
  let(:b2) {}
end

shared_examples 'R1' do
  it 'R1' do
    # ...
  end
end

shared_examples 'R2' do
  it 'R2' do
    # ...
  end
end

shared_examples 'R3' do
  it 'R3' do
    # ...
  end
end

context 'A1' do
  include_context 'A1'

  include_examples 'R1'

  context 'B1' do
    include_context 'B1'

    include_examples 'R1'
  end

  context 'B2' do
    include_context 'B2'

    include_examples 'R1'
  end
end

context 'A2' do
  include_context 'A2'

  include_examples 'R2'

  context 'B1' do
    include_context 'B1'

    include_examples 'R3'
  end

  context 'B2' do
    include_context 'B2'

    include_examples 'R2'
  end
end
Войти в полноэкранный режим Выход из полноэкранного режима

Хорошо, по крайней мере, теперь при изменении деталей теста или контекстного случая мне не придется обновлять все места.

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

Представьте себе что-то вроде приведенного ниже фрагмента. Я перевел вложенные контексты и примеры из предыдущего блока кода так, чтобы в каждой строке последний элемент массива определял ожидаемую группу shared_examples, а все предыдущие элементы были именами shared_context, которые должны быть глубоко вложены один за другим:

[
  ['A1', 'R1'],
  ['A1', 'B1', 'R1'],
  ['A1', 'B2', 'R1'],
  ['A2', 'R2'],
  ['A2', 'B1', 'R3'],
  ['A2', 'B2', 'R2'],
].each do |scenario|
  # ... do some Ruby + rspec magic here
end
Вход в полноэкранный режим Выйти из полноэкранного режима

Читается гораздо лучше, не так ли? Давайте посмотрим, как выглядит полное решение:


[
  ['A1', 'R1'],
  ['A1', 'B1', 'R1'],
  ['A1', 'B2', 'R1'],
  ['A2', 'R2'],
  ['A2', 'B1', 'R3'],
  ['A2', 'B2', 'R2'],
].each do |scenario|
  # All but last element of the array
  contexts = scenario[0..-2]
  test_example = scenario.last

  # Let's prepare a deeply nested set of Procs with all the required nesting to achieve the same result. 
  # A tricky thing to understand here is that we need to start from the most nested block and go up,
  # until the most outer context.
  contexts.reverse.inject(proc { include_examples(test_example) }) do |inner, ctx|
    proc do
      context ctx do
        include_context(ctx, &inner)
      end
    end
  end.call
end
Вход в полноэкранный режим Выйти из полноэкранного режима

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

Если вы знаете более простой или лучший метод (гем Ruby? встроенная функциональность RSpec?) решения подобных проблем, пожалуйста, сообщите мне об этом в комментарии или личном сообщении.

Оцените статью
devanswers.ru
Добавить комментарий