Note: the question "should I test private methods or only public ones?" is a great reference to what I'm asking.
我的问题是:用复杂的私有(private)方法构建一个单一的、防弹可靠的公共(public)方法的最实用的 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
。太棒了,这就是我所关心的。我只想要培根。
但是说我的仆人有点烂。那是因为他还没有任何私有(private)方法,所以 gimme_bacon
只返回 nil
。好吧,没问题,我是一名开发人员,我将为 Servant 类提供所有正确的私有(private)方法,以便他最终可以 gimme_bacon
。
在我追求可靠的仆人的过程中,我想对他的所有方法进行 TDD。但是等等,我只关心他会 gimme_bacon
。我真的不在乎他必须采取的所有步骤,只要我在一天结束时得到培根即可。毕竟,gimme_bacon
是唯一的公共(public)方法。
所以,我这样写我的测试:
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
不错。我只测试了公共(public)方法。完美,100% 的测试覆盖率。我继续进一步开发 gimme_bacon
功能,并完全相信它正在接受测试。
第 2 章)编写 moar 私有(private)方法
经过一些开发(不幸的是,不是 TDD,因为我添加了私有(private)方法)我可能有这样的东西(伪代码):
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
结论:
事后看来,这是很多私有(private)方法。
可能有多个失败点,因为我并没有真正对其中任何一个进行测试驱动开发。上面是一个简单的例子,但是如果仆人必须做出决定,比如根据我的要求去哪家杂货店呢?如果互联网出现故障并且他无法使用 google 等怎么办。
是的,你可以说我或许应该创建一个子类,但我不太确定。我想要的只是一个具有一个公共(public)方法的类。
为了将来引用,我可以在我的 TDD 过程中做得更好吗?
最佳答案
我不确定您为什么会这样想,因为它们是私有(private)方法,不能进行 TDD。它们是私有(private)方法(或 50 个不同的类)这一事实是测试培根仆人所需行为的实现细节。
为了在你的类中的私有(private)方法中完成所有的事情
- 相关性
- 输入
否则它会像第一个示例一样返回一些培根。
这些输入和依赖项是在您进行 TDD 时插入测试的关键,即使这些输入会导致私有(private)方法。您仍然只能通过公共(public)接口(interface)进行测试
因此,在您的第二个示例中,您有一些规范要通过 gimme_bacon 方法传递给您的类(class)( 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 中很简单),这样您就可以轻松地测试类边界处发生的情况。所以在你的例子中你可能有一个食谱查找器。你把这个传给你的类(class),并在你的测试中给它
- 寻找食谱的人
- 没有找到
- 一个错误
- 等等
- 等等
每次你写一个测试来说明当它的依赖项以某种方式表现时你期望你的类的行为是什么。然后,您创建一个以这种方式运行的依赖项,并在您的类中实现所需的行为。
所有 TDD,无论这些方法是否私有(private)。
关于unit-testing - 具有多个复杂私有(private)方法的单个公共(public)方法的 TDD 过程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39412125/