这是我在RSpec测试中尝试做的事情 –
describe MySingleton,"#not_initialised" do it "raises an exception" do expect {MySingleton.get_something}.to raise_error(RuntimeError) end end
它失败了,因为我以前的一个测试之一初始化单例对象.我已经尝试过跟随Ian White的建议,从this链接,基本上猴子补丁Singleton提供一个reset_instance方法,但我得到一个未定义的方法’reset_instance’异常.
require 'singleton' class <<Singleton def included_with_reset(klass) included_without_reset(klass) class <<klass def reset_instance Singleton.send :__init__,self self end end end alias_method :included_without_reset,:included alias_method :included,:included_with_reset end describe MySingleton,"#not_initialised" do it "raises an exception" do MySingleton.reset_instance expect {MySingleton.get_something}.to raise_error(RuntimeError) end end
在Ruby中最常用的方法是什么?
解决方法
有几件事你可以做,他们都是“好的”,最好的.最好的解决办法是找出一种摆脱单身人士的方式.这是手工波浪的,我知道,因为没有一个公式或算法可以应用,它消除了很多方便,但如果你能做到这一点,这通常是值得的.
如果你不能这样做,至少尝试注入单身,而不是直接访问它.测试现在可能很难,但是想象在运行时必须处理这样的问题.为此,您需要内置的基础设施来处理它.
这是我想到的六种方法.
提供类的一个实例,但允许该类被实例化.这最符合单身人士传统上的方式.基本上任何时候你想参考单身人士,你都可以跟单身人士进行谈话,但是你可以测试其他的例子.在stdlib中有一个模块来帮助这个,但它使.new是私有的,所以如果你想使用它,你将不得不使用let(.config){Configuration.send:new}来测试它.
class Configuration def self.instance @instance ||= new end attr_writer :credentials_file def credentials_file @credentials_file || raise("credentials file not set") end end describe Config do let(:config) { Configuration.new } specify '.instance always refers to the same instance' do Configuration.instance.should be_a_kind_of Configuration Configuration.instance.should equal Configuration.instance end describe 'credentials_file' do specify 'it can be set/reset' do config.credentials_file = 'abc' config.credentials_file.should == 'abc' config.credentials_file = 'def' config.credentials_file.should == 'def' end specify 'raises an error if accessed before being initialized' do expect { config.credentials_file }.to raise_error 'credentials file not set' end end end
然后在任何您要访问它的地方,使用Configuration.instance
使单身人士成为其他类别的榜样.那么你可以隔离测试另一个类,并且不需要明确地测试你的单例.
class Counter attr_accessor :count def initialize @count = 0 end def count! @count += 1 end end describe Counter do let(:counter) { Counter.new } it 'starts at zero' do counter.count.should be_zero end it 'increments when counted' do counter.count! counter.count.should == 1 end end
然后在你的应用程序的某个地方:
MyCounter = Counter.new
你可以确保永远不要编辑主类,然后将它们子类化为你的测试:
class Configuration class << self attr_writer :credentials_file end def self.credentials_file @credentials_file || raise("credentials file not set") end end describe Config do let(:config) { Class.new Configuration } describe 'credentials_file' do specify 'it can be set/reset' do config.credentials_file = 'abc' config.credentials_file.should == 'abc' config.credentials_file = 'def' config.credentials_file.should == 'def' end specify 'raises an error if accessed before being initialized' do expect { config.credentials_file }.to raise_error 'credentials file not set' end end end
然后在你的应用程序的某个地方:
MyConfig = Class.new Configuration
确保有一种方式来重置单身人士.或者更一般地,撤消你做的任何事情. (例如,如果您可以使用单例注册一些对象,那么您需要能够在Rails中注销它,例如,当您将子类化为Railtie时,它会记录在阵列中,但可以在access the array and delete the item from it中).
class Configuration def self.reset @credentials_file = nil end class << self attr_writer :credentials_file end def self.credentials_file @credentials_file || raise("credentials file not set") end end RSpec.configure do |config| config.before { Configuration.reset } end describe Config do describe 'credentials_file' do specify 'it can be set/reset' do Configuration.credentials_file = 'abc' Configuration.credentials_file.should == 'abc' Configuration.credentials_file = 'def' Configuration.credentials_file.should == 'def' end specify 'raises an error if accessed before being initialized' do expect { Configuration.credentials_file }.to raise_error 'credentials file not set' end end end
克隆课程,而不是直接测试.这是从gist出来的,基本上你编辑的是克隆而不是真正的类.
class Configuration class << self attr_writer :credentials_file end def self.credentials_file @credentials_file || raise("credentials file not set") end end describe Config do let(:configuration) { Configuration.clone } describe 'credentials_file' do specify 'it can be set/reset' do configuration.credentials_file = 'abc' configuration.credentials_file.should == 'abc' configuration.credentials_file = 'def' configuration.credentials_file.should == 'def' end specify 'raises an error if accessed before being initialized' do expect { configuration.credentials_file }.to raise_error 'credentials file not set' end end end
在模块中开发行为,然后将其扩展到单例. Here是一个稍微涉及的例子.如果您需要初始化对象上的某些变量,您可能需要查看self.included
和self.extended
方法.
module ConfigurationBehavIoUr attr_writer :credentials_file def credentials_file @credentials_file || raise("credentials file not set") end end describe Config do let(:configuration) { Class.new { extend ConfigurationBehavIoUr } } describe 'credentials_file' do specify 'it can be set/reset' do configuration.credentials_file = 'abc' configuration.credentials_file.should == 'abc' configuration.credentials_file = 'def' configuration.credentials_file.should == 'def' end specify 'raises an error if accessed before being initialized' do expect { configuration.credentials_file }.to raise_error 'credentials file not set' end end end
然后在你的应用程序的某个地方:
class Configuration extend ConfigurationBehavIoUr end