2012-08-25 38 views
1

现在我已经甩了我三天了。我创建了一个类车型的HTML页面,并告诉黄瓜的步骤定义,其中填充表单数据:这是define_method用例太复杂吗?

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将创建方法,以允许黄瓜来填充它。

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

+0

我不完全确定我的理解,但是:你不想读取类加载的配置文件,然后添加类级方法吗? –

+0

你不明白的是对我来说,我想要做的事有点奇怪。你的意思是你希望看到类定义之外的需求和文件加载?正如你所看到的,我是一个新手 - 我真的是一个测试者,而不是开发者 –

+0

(移动评论以回答空间的原因,但它不是*答案。) –

回答

1

这是元编程适当的情况下,但它看起来像你要去了解它的错误的方式。

首先,FlightSearchPage的每个实例会有不同的配置文件,或者只有一个配置文件控制所有页面?它看起来像加载相同的配置文件,无论initialize的参数,所以我猜你的情况是前者。

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

在另一方面,如果你想为每个实例不同的配置,你需要所有的元编程代码移动到单独的类如define_singleton_method而不是define_method

+0

谢谢Max。这似乎也是你已经钉住了我的范围问题。这个想法是,配置文件被编辑了测试框架的每个实现的适当的值,所以是的,前者。而且我强烈怀疑我是以错误的方式去做的。我对元编程非常陌生。再次感谢。 –

+0

另一个问题 - 如果我在类中加载配置文件,是否需要使用类变量? –

+0

如果您使用元编程,您根本不需要类变量:配置文件中的信息将被“存储”在您创建的类的结构中(例如,在运行时定义的实例方法)。另一方面,你完全可以通过将配置文件信息存储在类变量中来避免元编程。这一切都取决于你认为课堂上最简单的工作。 – Max

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被调用。

+0

好的,我需要一点时间吸收这一点。感谢您一直以来的帮助。 –

+0

我认为我想要做的是不同的。实例方法(set_xxx)标识一个特定的html表单元素,并使用传入的参数值设置元素。因此,例如,它可以使用值'EDI'设置ID为'departure_port'的元素。这意味着像set_origin('EDI'),这将设置浏览器中的物理文本字段的值'EDI' –

+0

@Rogue_Leader我没有看到它在概念层面有什么不同。 setter使用有关该字段的元数据(如选择器)来访问该页面。有什么不同?你甚至不需要“set_xxx”,你可以使用“xxx =” –