在rspec单元测试中模拟选择期望值

时间:2016-07-27 22:21:54

标签: ruby unit-testing rspec mocking

如果我对这个问题的描述不好,请纠正我。

假设以下代码:

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状态的组合都没有),这让我觉得我实际上不是 unit < / em>测试方法,但是集成测试它,因为我让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,请考虑移动{{1 - 语句只将测试需要移到另一个位置。

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

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

Ps 2.我正在强调需要单元而不是集成测试的简短版本是这个视频:https://www.youtube.com/watch?v=VDfX44fZoMc

编辑: 一个更好的方法来表达这个问题可能是:第一个测试是古典主义者如何测试select的一个例子,但 mockist 测试一下吗?

2 个答案:

答案 0 :(得分:0)

我不是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

答案 1 :(得分:0)

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

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之外阵列。