2012-08-25 42 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 =」 –