开发者

Ruby idiom for running code twice ("in-between code")

In my Capybara+Webdriver AJAX tests, I see a pattern of code like this:

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

I extracted this into a persist helper function, which does

开发者_C百科def persist
  yield
  visit current_page
  yield
end

Question: Is there a compact idiom to do the same thing inline, without a helper function?

The shortest I've been able to come up with is

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

which is DRY but still ugly.

Edit: I think Mark's comment is quite right, and I'm sticking with my persist helper for this particular use case. That said, there's several good (and interesting) ideas in the answers below.

Edit 2: In case anybody wants to copy my persist example: With RSpec, it's useful to put a @__memoized = {} after visit current_page so as to refresh any lets holding nodes that will go outdated after the page reload (else you get an ObsoleteElementError).


In my opinion, there's nothing wrong with repeating a line if it makes the code more readable. Your case seems to be a good example of such justified repetition :)

If you find yourself repeating the pattern a lot, you may want to extend Object with something like

def should_still(predicate)
  should predicate
  yield
  should predicate
end

then you can write compact statements like

page.should_still have_selector('foo.bar > baz') { visit current_page }


You can make a general-purpose version following the pattern of 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

Then, this:

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

Will produce this output:

Hello
World
Hello

EDIT: Actually, now that I think of it, here's a much easier way that also reads quite nicely in usage:

def around(inner)
  yield
  inner.call
  yield
end

around(lambda{puts "World"}) do
  puts "Hello"
end


In this case, I would steer you away from "clever" solutions. Tests are supposed to be fairly procedural recipes, and if you abstract into something like your example, the test loses its readability. I can easily understand the intent and purpose of the first test, but the "condensed" version is as clear as mud.

In this case, I would encourage you to not necessarily feel like you need to avoid code duplication if it costs you clarity. If you still want to abstract it, I would recommend something like a helper, like

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

Then,

def after_refresh(&block)
  yield
  visit current_page
  yield
end

That keeps the test expressive, and lets you avoid duplication if you're testing a lot of these refresh cases.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜