模拟 ruby-core 类 的正确方法

Proper way to mock ruby-core classes

我想知道这个解决方案是否得到了 ruby 中模拟对象的社区认可。如果不是,请说明原因,以及如何改进代码设计或测试。

假设我有以下代码。

lib/config_loader.rb

class ConfigLoader
  HOME_DIR = '~/my/home_dir'.freeze
  ...

  def load(path)
    path = File.expand_path(path)

    if File.exist?(path)
      File.read(path) 
    else
      File.read(HOME_DIR)
    end
  end
end

在测试时,我实际上不想在我在变量中定义的 path 下创建任何东西,所以我只想模拟这种行为

spec/lib/config_loader_spec.rb

RSpec.describe ConfigLoader do
  describe '.load' do
    subject { described.class.new.load(path) }

    let(:path) { 'path/that/should/exist' }

    context 'when path' do
      before do 
        allow(File).to receive(:exist?).with(path).and_return(true)
        allow(File).to receive(:read).with(path).and_return('my content')
      end

      it { expect { subject }.to_not raise_error { Errno::ENOENT }
    end
  end
end

也许我应该做一些 class_double of File Class。我不确定我提供的方式,所以我需要一些信息如何以 common/best-practices 方式

Test-Driven / Behavior-Driven Development/Design 的基本 信条之一是不要嘲笑你所做的't Own(在书中创造 Growing Object-Oriented Software, Guided by Tests by Steve Freeman and Nat Pryce)。

您的示例违反了这一原则:您不拥有 File,因此您不应该嘲笑它。

相反,您可以创建您自己的抽象来与文件系统交互,它只具有您实际需要的功能。然后您可以创建此抽象的两个实现:一个使用 Ruby 的 File class,一个模拟的什么都不做。如果你想花点心思,你甚至可以创建一个在内存中模拟文件系统的系统。

当然,您现在只是解决了问题:现在您的文件抽象实现中有 未经测试 的代码。然而,理想情况下,这段代码应该“几乎”是微不足道的。显然,您仍然可以对文件抽象实现进行集成测试,还可以对 ConfigLoader 使用真实实现而不是模拟或模拟进行集成测试。

如果您有兴趣,这里有一些进一步的阅读: