Ruby идиома для запуска кода дважды («промежуточный код») - PullRequest
2 голосов
/ 01 февраля 2011

В моих тестах AJAX на Capybara + Webdriver я вижу такой код:

page.should have_selector('foo.bar > baz')  # added dynamically by JS
visit current_page
page.should have_selector('foo.bar > baz')  # still there after reload

Я извлек это в persist вспомогательную функцию, которая делает

def persist
  yield
  visit current_page
  yield
end

Вопрос: есть ли компактная идиома, позволяющая делать то же самое в линии без вспомогательной функции?

Самое короткое, что я смог придумать, это

2.times { |i|
    page.should have_selector('foo.bar > baz')
    visit current_page if i == 0
}

который СУХОЙ, но все еще безобразный.

Редактировать: Я думаю, что комментарий Марка совершенно правильный, и я придерживаюсь своего помощника persist для этого конкретного варианта использования. Тем не менее, есть несколько хороших (и интересных) идей в ответах ниже.

Редактировать 2: В случае, если кто-то захочет скопировать мой persist пример: с RSpec полезно поставить @__memoized = {} после visit current_page, чтобы обновить let удерживающие узлы, которые устаревают после перезагрузки страницы (иначе вы получите ObsoleteElementError).

Ответы [ 3 ]

5 голосов
/ 01 февраля 2011

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

Если вы часто повторяете шаблон, вы можете расширить Object чем-то вроде

def should_still(predicate)
  should predicate
  yield
  should predicate
end

тогда вы можете написать компактные операторы типа

page.should_still have_selector('foo.bar > baz') { visit current_page }
3 голосов
/ 01 февраля 2011

Вы можете сделать универсальную версию, следуя шаблону http://groups.google.com/group/comp.lang.ruby/browse_thread/thread/1052c289b22c60a5

class AroundWrapper
  def initialize &block
    (class << self
      def self.outer &block
        define_method :outer, &block
      end
      def self.inner &block
        define_method :inner, &block
      end
      self
    end).class_eval &block
  end
end

def around &block
  around_wrapper = AroundWrapper.new &block
  around_wrapper.outer
  around_wrapper.inner
  around_wrapper.outer
end

Затем, это:

around {
  outer { puts "Hello" }
  inner { puts "World" }
}

Будет выдавать этот вывод:

Hello
World
Hello

РЕДАКТИРОВАТЬ : На самом деле, теперь, когда я об этом думаю, вот гораздо более простой способ, который также довольно хорошо читается при использовании:

def around(inner)
  yield
  inner.call
  yield
end

around(lambda{puts "World"}) do
  puts "Hello"
end
0 голосов
/ 01 февраля 2011

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

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

after_refresh { page.should have_selector("foo.bar > baz") }

Затем,

def after_refresh(&block)
  yield
  visit current_page
  yield
end

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...