2012-08-16 75 views
1

比方說,你有一個簡單的類,如:測試突變和存取方法時避免重複

class Box 
    def initialize 
    @widgets = [] 
    end 

    def add_widget(widget) 
    @widgets << widget 
    end 

    def widgets 
    @widgets 
    end 
end 

我會寫一個測試,看起來是這樣的:

describe Box do 
    describe "#initialize" do 
    it "creates an empty box" 
     Box.new.widgets.should == [] 
    end 
    end 

    describe "#add_widget" do 
    it "adds a widget to the box" 
     widget = stub('widget') 
     box = Box.new 
     box.add_widget(widget) 
     box.widgets.should == [widget] 
    end 
    end 

    describe "#widgets" do 
    it "returns the list of widgets" 
     widget = stub('widget') 
     box = Box.new 
     box.add_widget(widget) 
     box.widgets.should == [widget] 
    end 
    end 
end 

注意如何最後兩個測試最終是相同的。我正在努力避免這些重疊的情況。我在前兩種情況下隱式測試#widgets,但我覺得應該有一個明確的測試。但是,代碼與第二種情況相同。

如果一個類有3個公共方法,那麼我會期望至少有一個測試對應每個這些方法。我錯了嗎?

UPDATE

我通過羅恩傑弗里斯其建議不要測試簡單的存取發現this article

回答

0

我不確定你,至少在這種情況下,你可以避免代碼重複。在沒有構造函數的情況下,您必須使用一種方法來測試另一種方法。一個辦法可以改變你的測試,在這種情況下可能是以下幾點:

describe Box do 
    describe "#initialize" do 
    it "creates an empty box" 
     Box.new.widgets.should == [] 
    end 
    end 

    describe "#add_widget" do 
    before do 
     @widget = stub('widget') 
     @box = Box.new 
     @box.add_widget(@widget) 
    end 

    it "adds a widget to the box, which gets returned by #widgets" 
     @box.widgets.should == [@widget] 
    end 
    end 
end 
+0

非常感謝,但儘管更短,#add_widget和#widgets測試用例仍然相同。 – 2012-08-16 14:08:51

+0

@AndyWaite:我看到你現在得到了什麼,我會更新我的答案 – 2012-08-16 14:10:06

+0

看來你的修訂版本只包含2個測試,現在第二個測試結合了#add_widget和#widgets測試。我認爲這是一個完全合理的方法,但能夠得到一些其他觀點會很好。 – 2012-08-16 15:17:11

2

這是非常簡單的例子,當你可能說你不應該這樣的存取。但是,如果情況稍微複雜一點,或者訪問者不是真正的訪問者,但它內部有一些邏輯,並且您確實想要測試它,那麼您可以使用instance_variable_getinstance_variable_setObject

describe Box do 
    describe "#initialize" do 
    it "creates an empty box" do 
     Box.new.widgets.should == [] 
    end 
    end 

    describe "#add_widget" do 
    it "adds a widget to the box" do 
     widget = stub('widget') 
     box = Box.new 
     box.add_widget(widget) 
     box.instance_variable_get(:@widgets).should == [widget] 
    end 
    end 

    describe "#widgets" do 
    it "returns the list of widgets" do 
     widget = stub('widget') 
     box = Box.new 
     box.instance_variable_set(:@widgets, [widget]) 
     box.widgets.should == [widget] 
    end 
    end 
end 

但是,我想這不是很好的測試技術,因爲你是一個對象的內部狀態,所以每當內部實現的改變,你必須確保測試正在改變,即使干擾的公共接口班級沒有變化。

+1

+1提及你在大多數情況下不應該測試訪問器 – guillaume31 2012-08-17 07:34:30

+0

Piotr是完全正確的,當你想在測試單個方法方面完全有道德時,你需要獨立地測試它們,也就是創建一個測試雙所有的方法被刪除或被測試方法所取代,並且只有被測試的方法被留下,並且在這個人造測試環境中,它們應該按照預期行事。 – 2012-08-17 11:47:04

0

也許這是測試正在發聲嗎?如果是這樣,而且我在聽,我會聽到這樣的話:「夥計,停止測試方法,這是重要的行爲。」。對此我想有以下回應:

describe Box do 
    it "contains widgets" do 
    widget = stub('widget') 
    box = Box.new 

    box.add_widget(widget) 
    box.widgets.should == [widget] 
    end 

    it "has no widgets by default" do 
    Box.new.widgets.should == [] 
    end 
end 

由於#widgets是唯一的(公衆)的方式來訪問箱部件和#add_widget是增加他們的唯一辦法,沒有其他的方式來測試Box行爲,但要同時鍛鍊它們。

+0

是的,我認爲問題是我試圖在實現的公共方法和測試用例之間進行直接映射。它應該更流暢。許多關於RSpec的文章似乎都促進了在測試名稱中使用方法名稱(#foo或.bar)的慣例,但也許這是一種不好的做法... – 2012-08-17 10:39:26

+0

@Andy不,不,我很瞭解你。在你開始覺得需要省略太簡單的測試之前,你的靈魂想要確保你對TDD誠實。 – 2012-08-17 11:41:10