在Ruby中重新设置单例实例

前端之家收集整理的这篇文章主要介绍了在Ruby中重新设置单例实例前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。
如何在Ruby中重置单例对象?我知道,他们从来不想在真实的代码中做到这一点,但是单元测试呢?

这是我在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中最常用的方法是什么?

解决方法

艰难的问题,单身人士粗暴.部分原因是您正在显示(如何重置),部分原因是它们假设有更多的咬住您的倾向(例如大多数Rails).

有几件事你可以做,他们都是“好的”,最好的.最好的解决办法是找出一种摆脱单身人士的方式.这是手工波浪的,我知道,因为没有一个公式或算法可以应用,它消除了很多方便,但如果你能做到这一点,这通常是值得的.

如果你不能这样做,至少尝试注入单身,而不是直接访问它.测试现在可能很难,但是想象在运行时必须处理这样的问题.为此,您需要内置的基础设施来处理它.

这是我想到的六种方法.

提供类的一个实例,但允许该类被实例化.这最符合单身人士传统上的方式.基本上任何时候你想参考单身人士,你都可以跟单身人士进行谈话,但是你可以测试其他的例子.在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.includedself.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

猜你在找的Ruby相关文章