2012-08-16 73 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