【问题标题】:Mocking select with expectations in rspec unit test在 rspec 单元测试中用期望模拟选择
【发布时间】:2016-07-27 22:21:53
【问题描述】:

如果我的问题措辞不好,请纠正我。

假设以下代码:

class Filters
  def self.good ducks
    ducks.select(&:good?)
  end
end

当然可以像这样测试方法:

describe Filters, '#good' do
  let(:good) { double(:good, good?: true) }
  let(:bad)  { double(:bad, good?: false) }

  subject { Filters.good(ducks) }

  context 'none' do
    let(:ducks) { [] }
    it { is_expected.to eq [] }
  end

  context 'one good' do
    let(:ducks) { [good] }
    it { is_expected.to eq [good] }
  end

  context 'one bad' do
    let(:ducks) { [bad] }
    it { is_expected.to eq [] }
  end

  context 'one good and one bad' do
    let(:ducks) { [good, bad] }
    it { is_expected.to eq [good] }
  end

  context 'some good and some bad' do
    let(:ducks) { [good, bad, bad, good, bad, good] }
    it { is_expected.to eq [good, good, good] }
  end
end

但是列举所有的情况(当然不是所有的,但没有/一个/一些/很多对于真/假good状态的组合)让我觉得我实际上不是单位 测试该方法,但集成 测试它,因为我将select“泄漏”的行为知识“泄漏”到我的good 方法中,导致测试用例的数量成倍增加。从某种意义上说,我的测试用例正在测试select 方法的行为。

对我来说,使用模拟和检查(如检查而不是测试)方法是否正确委托似乎更合适。如果一个人与自己编写的函数集成,就可以做到这一点。类似于我们的方式,例如可以说expect(obj).to receive(:foo).with(:bar)

下面的示例不起作用,但我相信它传达了我想要做的想法,而不是枚举所有案例。

describe Filters, '#good' do
  it 'returns only good ones' do
    ducks = double(:ducks)
    expect(ducks).to receive(:filter).with(:good?).and_return(:filtered)
    expect(Filters.good(ducks).to eq )
  end
end

这个例子可能看起来微不足道,所以为了避免争论说这是一种无用的方法,Filter.good 的用户应该直接使用select,请考虑移动select-语句只会移动的事实需要到另一个地点进行测试。

我是否误解或忽略了一些基本的东西?

Ps 1. 为简洁起见,我忽略了一些测试用例。

Ps 2. 我强调需要 unit 而不是 integration 测试的原因的简短版本是这个视频:https://www.youtube.com/watch?v=VDfX44fZoMc

编辑: 表达这个问题的更好方法可能是:第一个测试是 classicist 如何测试Filters.good 的要点示例,但 mockist 将如何测试是吗?

【问题讨论】:

  • 您找到解决方案了吗?
  • @PaulFioravanti 感谢您提供的答案。但在我看来,目前还没有解决方案。我正在对问题进行澄清。

标签: ruby unit-testing rspec mocking


【解决方案1】:

我不能 100% 确定您要达到的目标,但我会考虑使用 Factory girl,尤其是 Factory Girls create_list。修改一个是坏的。 https://github.com/thoughtbot/factory_girl/blob/master/GETTING_STARTED.md 如果你真的想要可以期望每个人都有:好?呼吁它。 确保您的列表最后只有好的列表可能会更好。 举一个真实世界的例子来说明你的所作所为可能会有所帮助,但我认为创建一个工厂女孩名单将至少满足你的一个问题。

修改:

let(:duck){double :duck}
describe Filters, '#good' do
  it 'returns only good ones' do
    ducks = [duck, duck, duck]
    expect(ducks).to receive(:good).exactly(3).times
    Filters.good(ducks)
  end
end

【讨论】:

  • 感谢您的回答。我已经改写了问题并添加了示例。
  • 好的,我会修改我的答案:P
【解决方案2】:

由于被测方法进行过滤,我认为您可以编写一个简洁的单个单元测试,而无需在方法中存根任何代码(除了 good? 的响应,由通过的集合的任何元素in) 通过测试结果集中是否包含(或不包含)对象:

class Filters
  def self.good(ducks)
    ducks.select(&:good?)
  end
end

RSpec.describe Filters do
  describe '.good' do
    let(:good_duck) { double(:good, good?: true) }
    let(:bad_duck) { double(:bad, good?: false) }
    let(:ducks) { [good_duck, bad_duck] }
    let(:good) { described_class.good(ducks) }

    it 'returns only good ducks' do
      expect(good).to include(good_duck)
      expect(good).not_to include(bad_duck)
    end
  end
end

规范不关心传递给Filters.good 方法的集合中有多少好鸭子或坏鸭子:它只关心响应good?true 的任何对象是否在结果集中,并且任何以false 响应的对象都不是,因此不需要问题中概述的context 块,除了测试空数组的context

【讨论】:

  • 但这会给实施带来误报:[ducks.first],这再次表明需要测试多个案例。
  • @chris 抱歉,我不明白您所说的“实施误报:[ducks.first]”是什么意思。包含集合的第一个元素的数组在哪里起作用?
  • 如果Filters.good(ducks) 的实现是[ducks.first],则测试通过(错误地)。
  • 那么您不是完全改变了方法实现,因此需要完全不同的测试吗? Filters.good(ducks)的当前实现是ducks.select(&:good?)的返回值,但是你想把实现改成[ducks.first]的返回值?抱歉,我还是没听懂……
  • 测试的目的是增加方法的实现不改变为不符合规范的东西的可能性。如果该方法不符合规范,那么我们应该看到一个失败的测试。这种情况下的规范(抱歉不清楚)是只应返回一个数组,该数组仅包含对消息true 响应 good? 的鸭子。通过上面的测试,如果将实现更改为不正确的实现def good(ducks); [ducks.first]; end,我们不会得到失败的测试。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-01-02
  • 1970-01-01
  • 2020-03-06
  • 2021-04-24
  • 2021-01-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多