提问人:binarymason 提问时间:9/9/2016 最后编辑:binarymason 更新时间:9/10/2016 访问量:150
单个公共方法与多个复杂私有方法的 TDD 流程
TDD process for single public method with multiple complex private methods
问:
注意:“我应该测试私有方法还是只测试公共方法?”这个问题很好地参考了我所问的问题。
我的问题是:用复杂的私有方法构建一个单一的、无懈可击的可靠公共方法,最实用的TDD过程是什么?
我最好通过例子来学习,所以这里是:
第 1 章)测试覆盖率
假设我有一个只做一件事的 ruby 类,它给了我培根。
它可能看起来像这样:
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
但是说我的仆人有点烂。那是因为他还没有任何私人方法,所以只是返回.好吧,没问题,我是一名开发人员,我将为 Servant 类提供所有正确的私有方法,以便他最终可以.gimme_bacon
nil
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
第 2 章)编写 moar 私有方法
经过一些开发(不幸的是,不是 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 流程中可以做得更好吗?
答:
我不确定你为什么认为因为它们是私有方法,所以它们不能被TDD'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'
您可以在添加测试时逐步实现此功能,这些测试定义了培根提供程序的所需行为
当你不得不去谷歌的东西外部时,你就会与依赖项进行交互。你的类应该允许切换这些依赖项(我相信这在 ruby 中很简单),这样你就可以很容易地测试在类的边界上发生了什么。因此,在您的示例中,您可能有一个配方查找器。你把它传给你的班级,在你的测试中你给它
- 一个找到食谱的人
- 一个找不到的
- 一个错误
- 等
- 等
每次你写一个测试,说明你期望你的类在依赖关系以某种方式表现时的行为是什么。然后,创建一个以这种方式运行的依赖项,并在类中实现所需的行为。
所有 TDD'd,无论这些方法是否是私有的。
评论
当一个类变得非常复杂时,可能是时候通过将部分委派给一些下属类来分解它了。想想单一责任原则。主类负责编排培根过程,有一个类查找食谱等。每个从属类都可以通过一个公共方法进行 TDD,该方法包含其行为的所有不同变体。对于主类,我只会做一些集成测试,以确保所有内容都正确地连接在一起。
评论