模拟 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
使用真实实现而不是模拟或模拟进行集成测试。
如果您有兴趣,这里有一些进一步的阅读:
- That's Not Yours, Eric Smith (8th Light)
- Don't mock what you don't own, Testdouble.com
- TDD: Only mock types you own, Mark Needham (Neo4J)
- Don't Mock What You Don't Own, Maksim Ivanov
- Don't Mock What You Don't Own, Matt's Codecave
- Don’t mock what you don’t own: a real world scenario, Giovanni Pinto
- Don't mock types you don't own, David Tchepak
我想知道这个解决方案是否得到了 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
使用真实实现而不是模拟或模拟进行集成测试。
如果您有兴趣,这里有一些进一步的阅读:
- That's Not Yours, Eric Smith (8th Light)
- Don't mock what you don't own, Testdouble.com
- TDD: Only mock types you own, Mark Needham (Neo4J)
- Don't Mock What You Don't Own, Maksim Ivanov
- Don't Mock What You Don't Own, Matt's Codecave
- Don’t mock what you don’t own: a real world scenario, Giovanni Pinto
- Don't mock types you don't own, David Tchepak