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
中存根两个方法。这两种方法是includes
和where
。这样做将阻止 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 产生的实际方法中的选择相匹配。如果被测方法中的参数发生更改,则测试将停止通过。这不是万无一失的,但我认为它已经足够接近了。