the*_*oid 20 ruby singleton rspec rspec2
如何在Ruby中重置单个对象?我知道在真正的代码中我们永远不想这样做但是单元测试呢?
这是我在RSpec测试中尝试做的事情 -
describe MySingleton, "#not_initialised" do
it "raises an exception" do
expect {MySingleton.get_something}.to raise_error(RuntimeError)
end
end
Run Code Online (Sandbox Code Playgroud)
它失败是因为我之前的一个测试初始化了单例对象.我试过从这个链接开始关注Ian White的建议,它基本上是猴子补丁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
Run Code Online (Sandbox Code Playgroud)
在Ruby中最常用的方法是什么?
Jos*_*eek 28
棘手的问题,单身人士很粗暴.部分原因在于您正在展示(如何重置它),部分原因是它们做出的假设有时候会让您感到困惑(例如大多数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
Run Code Online (Sandbox Code Playgroud)
然后,您想要访问它的任何地方,使用 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
Run Code Online (Sandbox Code Playgroud)
然后在你的应用程序中某处:
MyCounter = Counter.new
Run Code Online (Sandbox Code Playgroud)
您可以确保永远不会编辑主类,然后将其子类化为您的测试:
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
Run Code Online (Sandbox Code Playgroud)
然后在你的应用程序中某处:
MyConfig = Class.new Configuration
Run Code Online (Sandbox Code Playgroud)
确保有一种方法可以重置单例.或者更一般地说,撤消你做的任何事情.(例如,如果你可以用单例注册一些对象,那么你需要能够在Rails中取消注册它,例如,当你子类化时Railtie
,它会在数组中记录它,但你可以访问数组并从中删除项目它).
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
Run Code Online (Sandbox Code Playgroud)
克隆类而不是直接测试它.这是出自我的要点,基本上你编辑克隆而不是真正的类.
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
Run Code Online (Sandbox Code Playgroud)
在模块中开发行为,然后将其扩展到单例.这是一个稍微复杂的例子.如果你需要在对象上初始化一些变量,你可能需要查看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
Run Code Online (Sandbox Code Playgroud)
然后在你的应用程序中某处:
class Configuration
extend ConfigurationBehaviour
end
Run Code Online (Sandbox Code Playgroud)
小智 24
我想这样做会解决你的问题:
describe MySingleton, "#not_initialised" do
it "raises an exception" do
Singleton.__init__(MySingleton)
expect {MySingleton.get_something}.to raise_error(RuntimeError)
end
end
Run Code Online (Sandbox Code Playgroud)
或者甚至更好地在回调之前添加:
describe MySingleton, "#not_initialised" do
before(:each) { Singleton.__init__(MySingleton) }
end
Run Code Online (Sandbox Code Playgroud)
如果你以前有过这个
let(:thing) { MyClass.instance }
Run Code Online (Sandbox Code Playgroud)
改为执行此操作
let(:thing) { MyClass.clone.instance }
Run Code Online (Sandbox Code Playgroud)
摘自已接受的较长答案。
归档时间: |
|
查看次数: |
8481 次 |
最近记录: |