【问题标题】:Is this define_method use case too complex?这个define_method用例是不是太复杂了?
【发布时间】:2012-08-20 20:50:08
【问题描述】:

我已经为此苦恼了大约三天了。我创建了一个类来模拟 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 来创建动态保存引用的变量,并且变量名称和值由配置文件提供(该配置文件旨在供非一定熟悉 Ruby)。

不幸的是,这是一个大而多毛的类,每次我想添加一个新字段时,我都必须编辑源代码,这显然是糟糕的设计,所以我一直在尝试更进一步并创建使用 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吗?

【问题讨论】:

  • 我不确定我是否理解,但是:您不想在类加载时读取配置文件并添加类级方法吗?
  • 你不明白这表明我正在尝试做的事情有点奇怪。你的意思是你希望看到类定义之外的需求和文件加载?如您所见,我是新手 - 我真的是测试人员,而不是开发人员
  • (由于空间原因,移动 cmets 来回答,但它不是答案。)

标签: ruby oop metaprogramming


【解决方案1】:

这是元编程的合适案例,但看起来你做错了。

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

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

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

【讨论】:

  • 谢谢马克斯。您似乎也解决了我的范围问题。这个想法是使用测试框架的每个实现的适当值编辑配置文件,所以是的,前者。我强烈怀疑我的做法是错误的。我对元编程很陌生。再次感谢。
  • 另一个问题——如果我在类中加载配置文件,是否需要使用类变量?
  • 如果您使用元编程,则根本不需要类变量:配置文件中的信息将“存储”在您从中创建的类的结构中(例如实例在运行时定义的方法)。另一方面,您可以通过将配置文件信息存储在类变量中来完全避免元编程。这一切都取决于您认为使课程最容易使用的因素。
  • 这不是关于易用性,而是更多关于灵活性 - 我希望能够从哈希键(来自 YAML 文件)创建方法并从哈希值派生参数。这使没有 Ruby 技能的人能够配置 Page 对象,并且无需更改类源代码。如果我将方法放在类变量中,每次需要添加或修改现有字段时,我都必须打开类定义并编辑源代码。
【解决方案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。

【讨论】:

  • 好的,我需要一点时间来吸收这个。感谢您迄今为止的帮助。
  • 我认为我正在尝试做的是不同的。实例方法 (set_xxx) 标识一个特定的 html 表单元素,并使用传入的参数值设置该元素。因此,例如,它可以将一个 id 为“departure_port”的元素设置为“EDI”。这意味着类似于 set_origin('EDI'),它将在浏览器中将物理文本字段设置为值 'EDI'
  • @Rogue_Leader 我看不出它在概念层面有何不同。 setter 使用有关字段的元数据(如选择器)来访问页面。有什么不同?你甚至不需要“set_xxx”,你可以使用“xxx="
  • "你甚至不需要 "set_xxx",你可以使用 "xxx=" " - 哦,当然!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-11-30
  • 1970-01-01
相关资源
最近更新 更多