在测试突变器和访问器方法时避免重复



假设你有一个简单的类,如下所示:

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 个公共方法,那么我希望每个方法至少有一个测试。我错了吗?

更新

我发现了Ron Jeffries的这篇文章,它建议不要测试简单的访问器。

这是非常简单的情况,正如您所说,您可能不应该那样访问器。但是,如果情况稍微复杂一点,或者访问器不是真正的访问器,但它内部有一些逻辑,并且您真的想要测试它,那么您可以使用 Object 的instance_variable_getinstance_variable_set

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

但是,我想这不是很好的测试技术,因为您正在干扰对象的内部状态,因此每当内部实现发生变化时,即使该类的公共接口没有更改,您也必须确保测试已更改。

我不确定你,至少在这种情况下,你可以避免代码重复。如果没有采用小部件数组的构造函数,则必须使用一种方法来测试另一种方法。 在这种情况下,您可以更改测试的一种方法如下:

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

也许是测试是发声的?如果是这样,是我在听,我会听到类似的话:"伙计,停止测试方法。重要的是行为。我会用以下内容回应:

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行为,只能同时执行它们。

最新更新