存根从数据库绘制的对象



Rails 4.2 测试 with Minitest and Mocha

我想将我的单元测试保留在它正在测试的模型中 - 而不是测试另一个模型完成的功能。模型has_many :language_progresses,我正在尝试测试一种方法,该方法获取这些language_progresses的子集,并对调用它们的方法的输出求和,如下所示:

def outcome_month_score(outcome_area, year = Date.today.year, month = Date.today.month)
  lps = language_progresses.includes(:progress_marker).
    where("progress_markers.topic_id" => outcome_area.id)
  lps.inject(0){ |sum, lp| sum + lp.month_score(year, month) }
end

这是我的测试:

it "must calculate the outcome score for an outcome area in a month" do
  year = 2015
  month = 1
  pm1 = progress_markers(:skills_used)
  pm2 = progress_markers(:new_initiatives)
  pm_other = progress_markers(:devotional)
  lp1 = LanguageProgress.new progress_marker: pm1
  lp2 = LanguageProgress.new progress_marker: pm2
  lp_other = LanguageProgress.new progress_marker: pm_other
  lp1.stubs(:month_score).with(year, month).returns(1)
  lp2.stubs(:month_score).with(year, month).returns(2)
  lp_other.stubs(:month_score).with(year, month).returns(4)
  state_language.language_progresses << [lp1, lp2, lp_other]
  state_language.save
  _(state_language.outcome_month_score(topics(:social_development), year, month)).must_equal 3
end

我的问题是存根在方法中求和它们的输出之前停止工作。它们都返回 0,即使我在制作后检查它们在测试中工作正常。

我认为当language_progresses从数据库加载时,存根被取消了。

我应该如何测试此方法?

您的猜测是正确的 - 使用ActiveRecord的查询接口实例化新的LanguageProgress实例并忽略关系上的存根。

一般来说,对于像这样的方法——它使用了一个非平凡的查询表达式——我不会尝试孤立地测试它。该方法最有可能通过将 SQL 查询更改为无效的内容而中断,并且您编写的任何级别的单元测试都必须存根查询方法,以便您失去它运行的查询实际上是有效 SQL 的任何保证。

但是,在您的情况下,可能很难创建完全有效的语言进度并能够推断其月份分数。我建议你隔离方法的两个部分:检索,它涉及SQL,真的不能真正进行单元测试,以及计算,它很容易进行单元测试。

def progresses_for_area(outcome_area)
  language_progresses.includes(:progress_marker).
    where("progress_markers.topic_id" => outcome_area.id)
end
def outcome_month_score(outcome_area, year = Date.today.year, month = Date.today.month)
  progresses_for_area(outcome_area).inject(0){ |sum, lp| sum + lp.month_score(year, month) }
  # or:
  # progresses_for_area(outcome_area).map { |lp| lp.month_score(year, month) }.sum
end

那么你的测试将是:

it "must calculate the outcome score for an outcome area in a month" do
  year = 2015
  month = 1
  pm1 = progress_markers(:skills_used)
  pm2 = progress_markers(:new_initiatives)
  lp1 = LanguageProgress.new progress_marker: pm1
  lp2 = LanguageProgress.new progress_marker: pm2
  lp1.stubs(:month_score).with(year, month).returns(1)
  lp2.stubs(:month_score).with(year, month).returns(2)
  state_language.stubs(:progresses_for_area).returns([lp1, lp2])
  _(state_language.outcome_month_score(topics(:social_development), year, month)).must_equal 3
end

您可以编写一个单独的测试来验证progresses_for_area是否有效,这会更容易,因为您不需要计算任何month_scores。

我发现这样做的方法是在集合state_languages.language_progresses中存根两个方法。这两种方法是includeswhere。这样做将阻止 ActiveRecord 从数据库中检索 LanguageProgress 对象。因此:

it "must calculate the outcome score for an outcome area in a month" do
  year = 2015
  month = 1
  pm1 = progress_markers(:skills_used)
  pm2 = progress_markers(:new_initiatives)
  pm_other = progress_markers(:devotional)
  lp1 = LanguageProgress.new progress_marker: pm1
  lp2 = LanguageProgress.new progress_marker: pm2
  lp_other = LanguageProgress.new progress_marker: pm_other
  lp1.stubs(:month_score).with(year, month).returns(1)
  lp2.stubs(:month_score).with(year, month).returns(2)
  lp_other.stubs(:month_score).with(year, month).returns(4)
  state_language.language_progresses << [lp1, lp2, lp_other]
  state_language.language_progresses.stubs(:includes).returns state_language.language_progresses
  state_language.language_progresses.
    stubs(:where).
    with("progress_markers.topic_id" => topics(:social_development).id).
    returns state_language.language_progresses.select{ |lp|
      lp.progress_marker.topic.id == topics(:social_development).id
    }
  _(state_language.outcome_month_score(topics(:social_development), year, month)).must_equal 3
end

在这里,我使 includes 方法只需返回 language_progresses 集合,where 方法返回该集合的子集。

这样做的缺点是,我们不会测试我们正在测试的方法中的查询是否返回正确的记录 - 如果没有,测试仍然会通过,但真正的方法会给出错误的输出。这就是为什么我还指定了where存根的参数。这样,我可以合理地确定使用该参数,我在测试中所做的LanguageProgress的选择与 where 产生的实际方法中的选择相匹配。如果被测方法中的参数发生更改,则测试将停止通过。这不是万无一失的,但我认为它已经足够接近了。

最新更新