unit-testing - 具有多个复杂私有(private)方法的单个公共(public)方法的 TDD 过程

标签 unit-testing language-agnostic tdd

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/

相关文章:

java - 如何在 Selenium 3 中运行或迁移 html 测试套件?

arrays - 两个数组和数字——最佳算法

unit-testing - 单元测试中什么是正向测试和负向测试

测试 Chef 角色和环境

iphone - XCode 3.2.4 升级后做逻辑测试的问题

unit-testing - 如何测试方法返回正确的值并在另一个变量中设置正确的值?

php - 使用 PHPUnit 在 Symfony3 中测试服务/服务容器的优雅方式

multithreading - 多核系统上的并行问题之外的线程有什么用?

unit-testing - 单元测试文档

tdd - 如何测试流利的验证错误消息