如何使用Rspec在Ruby on Rails中测试链式方法

时间:2011-07-01 16:08:19

标签: ruby-on-rails testing rspec

我正在尝试决定如何测试一个简单计算相关记录值的平均值的方法。我担心测试实现与返回的实际结果。

说我有以下型号......

class User
  has_many :interviews

  def interview_grade
    interviews.average(:score).round unless interviews.empty?
  end
end

class Interview
  belongs_to :user
end

user_spec.rb我有......

describe "interview_grade" do
  let(:user) {User.new}
  context "when the user has interviews" do
    before { user.stub_chain(:interviews, :empty?){false} }
    it "should return an average of the appraisal ratings" do
      user.interviews.should_receive(:average).with(:score).and_return(3.2)
      user.work_history_grade.should == 3
    end
  end

  context "when the user has no interviews" do
    before {Interview.destroy_all}
    it "should return nil" do
      user.interview_grade.should be_nil
    end
  end

end

这些测试通过但对我来说感觉很脆弱。如果interview_grade实际上应该计算得分的总和,那该怎么办?因为我只是测试一个特定的方法链被调用,这个通过测试不会告诉我结果实际上是错误的。

我已经尝试对user.interviews进行存根以设置测试的可用分数,但由于关联延迟加载的方式,这在Rails 3中显得很棘手。即我不能只创建一个Interview对象数组,因为它不响应平均method

任何建议都非常感谢。

2 个答案:

答案 0 :(得分:1)

3年后再回到这里。我会以完全不同的方式处理它。

以下代码的好处是,为了为InterviewGrader编写测试,我不再需要担心如何获得分数。

我只是给它分数并测试它给我正确的输出。

此外,我永远不必担心InterviewGrader的底层实现。但是,如果逻辑在以后更改,测试将失败。

scores上的新User方法需要单独测试。

class InterviewGrader

  def self.run scores
    new(scores).run
  end

  attr_reader :scores

  def initialize(scores)
    @scores = scores
  end

  def run
    scores.inject { |sum, score|
      sum + score
    }.to_f / number_of_scores
  end

  private

  def number_of_scores
    scores.length
  end

end


class User
  has_many :interviews

  def scores
    interviews.map(&:score)
  end

  def interview_grade
    InterviewGrader.run(scores)
  end
end

class Interview
  belongs_to :user
end

答案 1 :(得分:0)

这是对存根和模拟的错误使用。

在这种情况下,您只应测试interview_grade何时有效average返回nil(这只是interviews.empty?使用的情况)。

average方法由rails本身测试。通过ruby测试的round方法(我猜)。所以你不需要测试这种方法。这是一个通用的想法,只测试你自己的代码。

如果你想测试,如何计算interview_grade,你应该创建测试数据(使用fixtures或工厂)。因为你应该单独测试(在某些情况下)系统的一部分,在这种情况下,分离是错误的:interview.average和interview.empty?依赖于您的代码,但在规范中它们是独立的。

def interview_grade
  interviews.average(:score).try(:round)
end

如果以这种方式重写方法,则无需使用存根和模拟