使用Minitest和Mocha进行Rails 4.2测试
我希望将我的单元测试保持在它正在测试的模型中 - 而不是测试另一个模型完成的功能。模型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,即使我检查它们在制作完成后在测试中工作正常。
我认为当从db加载language_progress时,存根被取消。
我该如何测试这种方法?
答案 0 :(得分:2)
您的猜测是正确的 - 使用ActiveRecord的查询接口实例化新的LanguageProgress实例并忽略关系上的存根。
一般来说,对于这样的方法 - 它使用了一个非平凡的查询表达式 - 我不会试图孤立地测试它。该方法最有可能通过将SQL查询更改为无效的方式来破解,并且您编写的任何级别的单元测试都必须删除查询方法,这样您就不会保证它运行的查询实际上是有效的SQL
但是,在您的情况下,可能很难创建完全有效的语言进展并能够推断他们的月份分数。我建议你分离方法的两个部分:检索,它涉及SQL,实际上不能真正单元测试,以及计算,这很容易进行单元测试。 / p>
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。
答案 1 :(得分:0)
我发现这样做的方法是在集合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
es匹配由where生成的实际方法中的那个。如果参数在测试方法中发生变化,则测试将停止传递。这不是万无一失的,但我认为它足够接近。