注意:问题 "should I test private methods or only public ones?" 非常适合我提出的问题。
我的问题是:使用复杂的私有方法构建单一,防弹的可靠公共方法,最实用的TDD 流程是什么?
我最好通过例子来学习,所以这里有:
说我有一个只做一件事的红宝石课,它给了我培根。
它可能看起来像这样:
class Servant
def gimme_bacon
# a bunch of complicated private methods go here
end
private
# all of the private methods required to make the bacon
end
现在我可以致电servant = Servant.new
; servant.gimme_bacon
。太棒了,这就是我所关心的。我想要的只是我的培根。
但是说我的仆人很糟糕。这是因为他还没有任何私人方法,所以gimme_bacon
只返回nil
。好吧,没问题,我是开发人员,我会给Servant类所有正确的私有方法,所以他最终可以gimme_bacon
。
在追求可靠的仆人时,我想要TDD他所有的方法。但等等,我关心的是他要去gimme_bacon
。只要我在一天结束时拿到培根,我真的不在乎他必须采取的所有步骤。毕竟,gimme_bacon
是唯一的公共方法。
所以,我写这样的测试:
RSpec.describe Servant do
let(:servant) { Servant.new }
it "should give me bacon when I tell it to!" do
expect(servant.gimme_bacon).to_not be_nil
end
end
尼斯。我只测试了公共方法。完美,100%的测试覆盖率。我继续进一步发展gimme_bacon
能力,并完全相信它正在接受测试。
经过一些开发(不幸的是,不是TDD,因为我添加了私有方法)我可能会有这样的东西(伪代码):
class Servant
attr_reader :bacon
def initialize(whats_in_the_fridge)
@bacon = whats_in_the_fridge[:bacon]
end
def gimme_bacon(specifications)
write_down_specifications(specifications)
google_awesome_recipes
go_grocery_shopping if bacon.nil?
cook_bacon
serve
end
private
attr_reader :specifications, :grocery_list
def write_down_specifications(specifications)
@specifications = specifications
end
def google_awesome_recipes
specifications.each do |x|
search_result = google_it(x)
add_to_grocery_list if looks_yummy?(search_result)
end
end
def google_it(item)
HTTParty.get "http://google.com/#q=#{item}"
end
def looks_yummy?(search_result)
search_result.match(/yummy/)
end
def add_to_grocery_list
@grocery_list ||= []
search_result.each do |tasty_item|
@grocery_list << tasty_item
end
end
def go_grocery_shopping
grocery_list.each { |item| buy_item(item) }
end
def buy_item
1_000_000 - item.cost
end
def cook_bacon
puts "#{bacon} slices #{bacon_size_in_inches} inch thick on skillet"
bacon.cooked = true
end
def bacon_size_in_inches
case specifications
when "chunky" then 2
when "kinda chunky" then 1
when "tiny" then 0.1
else
raise "wtf"
end
end
def serve
bacon + plate
end
def plate
"---"
end
end
事后看来,这是很多私人方法。
可能有多个失败点,因为我没有真正的TDD任何一个。以上是一个简单的例子,但如果仆人必须做出决定,比如根据我的规格去哪家杂货店怎么办?如果互联网出现故障并且无法进行谷歌等等该怎么办?
是的,你可以说我应该做一个子类,但我不太确定。我想要的只是一个有一个公共方法的类。
为了将来参考,我在TDD过程中可以做得更好吗?
答案 0 :(得分:2)
我不确定你为什么这么认为,因为他们是私人的方法,他们不能成为TDD&#39; d。它们是私有方法(或50个不同类)的事实是测试培根仆人所需行为的实现细节。
为了完成私有方法中的所有内容,你的类必须具有
否则它会像第一个例子中那样返回一些培根。
这些输入和依赖关系是在TDD时驱逐测试的关键,即使这些输入导致私有方法。您仍然只会通过公共接口进行测试
所以在你的第二个例子中,你有一些你在gimme_bacon方法中传递给你的类的规范(ruby不是我的东西,所以请原谅任何误解)。您的测试可能如下所示:
When I ask for chunky bacon I should get bacon that's 2" thick
When I ask for kinda chunky bacon I should get bacon that's 1" thick
When I ask for tiny bacon I should get bacon thats 0.1" thick
When I ask for an unsupported bacon chunkyness I should get an error telling me 'wtf'
您可以在添加定义培根提供商所需行为的测试时逐步实现此功能
当你不得不去google外部时,你就会有依赖的互动。你的类应该允许切换这些依赖项(我相信在ruby中很简单),这样你就可以轻松地测试在类边界发生的事情。所以在你的例子中你可能有一个食谱查找器。你将它传递给你的班级,并在你的测试中给出它
每次你编写一个测试,说明当你的依赖行为以某种方式运行时你期望你的类的行为。然后创建一个以这种方式运行的依赖项,并在您的类中实现所需的行为。
所有TDD&#39; d,无论这些方法是否属于私有。
答案 1 :(得分:1)
当一个类变得非常复杂时,可能是时候通过将片段委托给一些从属类来分解它。想想单一责任原则。主要课程负责协调培根过程,有一个类来查找食谱等。每个下属类可以通过公共方法进行TDD,其中包括其行为的所有不同变体。对于主类我只做一些集成测试,以确保所有内容都正确连接在一起。