1

我已经为此苦恼了大约三天了。我创建了一个类来模拟 html 页面并告诉黄瓜步骤定义在哪里填充表单数据:

class FlightSearchPage

  def initialize(browser, page, brand)
    @browser = browser
    @start_url = page

    #Get reference to config file
    config_file = File.join(File.dirname(__FILE__), '..', 'config', 'site_config.yml')

    #Store hash of config values in local variable
    config = YAML.load_file config_file

    @brand = brand #brand is specified by the customer in the features file

    #Define instance variables from the hash keys
    config.each do |k,v|
      instance_variable_set("@#{k}",v)
    end
  end

  def method_missing(sym, *args, &block)
    @browser.send sym, *args, &block
  end

  def page_title
    #Returns contents of <title> tag in current page.
    @browser.title
  end

  def visit
    @browser.goto(@start_url)
  end

  def set_origin(origin)
    self.text_field(@route[:attribute] => @route[:origin]).set origin
  end

  def set_destination(destination)
    self.text_field(@route[:attribute] => @route[:destination]).set destination
  end

  def set_departure_date(outbound)
    self.text_field(@route[:attribute]  => @date[:outgoing_date]).set outbound
  end

  # [...snip]

end

如您所见,我使用 instance_variable_set 来创建动态保存引用的变量,并且变量名称和值由配置文件提供(该配置文件旨在由不一定熟悉的人编辑红宝石)。

不幸的是,这是一个大而多毛的类,每次我想添加一个新字段时,我都必须编辑源代码,这显然是糟糕的设计,所以我一直在尝试更进一步并创建使用 define_method 动态设置变量名称的方法,这就是过去几个晚上让我一直睡到凌晨 4 点的原因。

这就是我所做的:

require File.expand_path(File.dirname(__FILE__) + '/flight_search_page')

class SetFieldsByType <  FlightSearchPage
  def text_field(config_hash)
    define_method(config_hash) do |data|
      self.text_field(config_hash[:attribute] => config_hash[:origin]).set data
    end
  end
end

这个想法是,添加新字段所需要做的就是向 YAML 文件添加一个新条目,并且 define_method 将创建允许 cucumber 填充它的方法。

目前,我遇到了范围问题——Ruby 认为 define_method 是@browser 的成员。但我想知道的是:这是否可行?我完全误解了define_method吗?

4

2 回答 2

2

你的意思是你希望看到类定义之外的需求和文件加载?

不,在类定义中。Ruby 类声明只是按照看到的顺序执行的代码。类似的东西attr_accessor只是类方法,碰巧对正在定义的类做一些事情,因为它正在被定义。这似乎是您想要做的。

在您的情况下,您将改为读取 YAML 文件,并运行您自己的逻辑来创建访问器、构建所需的任何支持数据等。我并不完全了解用例,但这听起来并不奇怪或困难——但.

也就是说,将这些定义放在 YAML 文件中会获得多少“便利”?考虑一下我曾经为创建用于驱动 Watir 的页面实例所做的事情:

class SomePage < HeavyWatir
  has_text :fname     # Assumed default CSS accessor pattern
  has_text :whatever, accessor: 'some accessor mechanism', option: 'some other option'
end

这些has_xxx是创建实例变量访问器的类方法(就像attr_accessor做的那样),构建了一些其他的数据结构,我用来确保页面上应该存在的所有内容实际上都是如此,等等。例如,非常粗略:

page = SomePage.new
page.visit
if page.missing_fields?
  # Do something saying the page isn't complete
end

听起来你想要一些模糊相似的东西:你有一堆“东西”你想给类(或子类,或者你可以将它混入任意类等)

这些“东西”具有附加功能,该功能以多种方式工作,例如:

定义期间发生的事情

例如,has_text将名称添加到页面元数据的类实例散列中,例如字段名称。

使用过程中发生的事情

例如,当fname=被调用时,在实例的元数据中设置一个标志,说明调用了 setter。

于 2012-08-25T17:34:39.083 回答
1

这是元编程的合适案例,但看起来您以错误的方式进行操作。

首先,FlightSearchPage 的每个实例是否会有不同的配置文件,还是只有一个控制所有页面的配置文件?无论参数如何,您似乎都在加载相同的配置文件,initialize所以我猜您的情况是前者。

如果是这样,您需要将所有元编程代码移动到类中(在方法定义之外)。即,当定义类时,您希望它加载配置文件,然后基于该配置创建每个实例。现在,您每次创建实例时都会重新加载配置文件,这似乎不正确。例如,define_method属于,Module所以它应该出现在类范围内,而不是在实例方法中。

另一方面,如果您确实想要为每个实例使用不同的配置,则需要将所有元编程代码移动到单例类中,例如define_singleton_method,而不是define_method.

于 2012-08-25T20:32:09.103 回答