我的理解是,单元测试应该单独测试类,关注粒度行为,并尽可能使用双精度/模拟替换其他类的对象。 (如果我错了,请纠正我。)
我正在写一个名为MatchList
的类的宝石。 MatchList::new
接受两个参数,每个参数都是另一个名为MatchPhrase
的类的实例。 MatchPhrase
包含MatchList
严重依赖的一些行为(即,如果您将MatchPhrase
以外的任何内容提供给MatchList::new
,那么您将会得到一堆“未定义的方法”错误)。
我当前(天真?)测试设置使用let
语句来分配变量以供我的示例使用:
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
let(:match_list) { MatchList.new(query, candidate) }
如何编写此单元测试?我是否认为应该在不调用MatchPhrase
类的情况下完成这项工作?这甚至可能吗?
供参考,这是MatchList
类的样子:
class MatchList < Array
attr_reader :query, :this_phrase, :that_phrase
def initialize(query, candidate)
super(query.length)
@query = query
@this_phrase = query.dup
@that_phrase = candidate
find_matches until none?(&:nil?)
end
private
def find_matches
query.each.with_index do |this_token, i|
next unless self[i].nil?
that_token = this_token.best_match_in(that_phrase)
next if that_token.match?(that_token) &&
this_token != that_token.best_match_in(this_phrase)
self[i] = this_token.match?(that_token) ? that_token : NilToken.new
this_phrase.delete_once(this_token)
that_phrase.delete_once(that_token)
end
end
end
答案 0 :(得分:2)
我的理解是,单元测试应该单独测试类,关注粒度行为,并尽可能使用双精度/模拟替换其他类的对象。 (如果我错了,请纠正我。)
根据我的理解,事实并非如此。 使用双打/模拟有利有弊。
优点是您可以使用数据库,电子邮件等慢速服务,并使用快速执行的对象进行模拟。
缺点是您正在嘲笑的对象不是“真实”对象,可能会让您感到惊讶并且行为与真实对象不同。
这就是为什么在实际使用真实物体总是更好的原因。 如果你想加速你的测试或者它导致更简单的测试,只使用模拟。即使这样,也有一个使用真实对象的测试来验证它是否全部有效。这称为集成测试。
考虑你的情况:
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
let(:match_list) { MatchList.new(query, candidate) }
模拟查询或候选人确实没有优势。
答案 1 :(得分:1)
您对使用测试部件的理解是正确的。它关注粒度行为。例如,个别方法。
但是,要测试各个方法,请尝试使用双击/模拟,不是可取的。 Marko ^^概述了模拟的优点/缺点。我个人不想尽可能多地使用双打/嘲笑。
它始终在测试速度和您创建的对象之间取得平衡。
在转向双打/模拟之前,最好先查看是否可以在不将值保存到数据库的情况下编写测试。就像你以前做过的那样。这比保存和检索数据库中的值要快。
还有一个问题是private
方法,而且通常不进行单元测试。这是在了解之后,您的私有方法的调用者将进行单元测试。
let(:query) { MatchPhrase.new('Good Eats') }
let(:candidate) { MatchPhrase.new('Good Grief') }
it 'check your expectation' do
expect(MatchList.new(query, candidate).query).to <check the expectation>
end
但是我会重新评估以下几点。
1 - 你想从初始化程序中调用find_matches
2 - 让find_matches
返回一个值,然后将其分配给@query
变量(以便使用返回值轻松测试该方法)
3 - 将init中的query
param重命名为其他内容(只是为了避免混淆)
黄金法则如果难以测试(特别是单元测试),也许你做错了。
HTH
答案 2 :(得分:1)
嘲笑应该出于正当理由而不是原则问题。
如果只有一个协作者类,并且您的主要类与它紧密相关,那么原则上嘲笑协作者可能会导致更多的脆弱而不是利益,因为模拟不会反映协作者的行为。
当您可以对模拟界面而不是实现进行推理时,模拟和存根是很好的选择。让我们忽略现有的代码并查看这里使用的接口:
MatchList.new
需要query
和candidate
query
是包含实现best_match_in?(something)
query
中的对象也实现了delete_once(something)
candidate
也实施了delete_once(something)
best_match_in?
会返回实现match?
和best_match_in?
查看正在使用的接口,MatchList
似乎非常依赖于query
和candidate
对象的实现。闻起来就像feature envy一样。也许这个功能应该位于MatchPhrase
内。
在这种情况下,我会使用带有注释的实际MatchPhrase
对象编写单元测试来重构此代码。