2009-12-15 76 views

回答

3

看看http://blog.ardes.com/2006/12/11/testing-singletons-with-ruby:“全局变量是坏,m'kay”

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 
+0

与此相关的问题是,当您的测试运行时,全局状态会被修改。请参阅下面的答案以获得更清晰的方式。 – 2014-07-11 20:41:39

0

RSpec是否允许您执行预测试操作?是这样的,你可以添加另一个方法给你静态类,在构造函数中清除所有的东西。然后在每个测试之前调用它。

+0

RSpec具有“之前”和“之后”的块。 http://rspec.info/documentation/before_and_after.html – 2009-12-15 17:58:01

+1

但是由于Singleton模式,构造函数不会被调用两次。 – brainfck 2009-12-15 17:59:56

1

将它重构为可以多次构建的类。这具有从类中去除Singleton特性的副作用(有些人会说是好处)。

再看另一种方式:您已经发现需要多次调用构造函数。为什么应该该类只构造一个实例?辛格尔顿提供什么好处?

+0

构造函数不需要多次调用,但我想保持我的规格清晰 - 所以我只测试每个规范中构造函数的一部分。 – brainfck 2009-12-15 17:59:20

-1

您可以简单地制作一个新的it并为每个规格块。将你的规格分解成可测试的单元。使用“之前”和“之后”来设置和清除你所做的任何事情。

before(:each)after(:each)每隔it块被执行。

2

原因之一的人使用,因为单身单例是一个全局变量,被隔离在一个名字空间中,并且具有懒惰的实例化。考虑一个真正的全局变量是否可以简化事情,特别是如果你不需要延迟实例化。

3

我见过的一种模式是将单例作为真实类的一个子类。您在生产代码中使用Singleton版本,但在测试中使用基础(非单例)类。

例子:

class MyClass 
    attr_accessor :some_state 

    def initialize 
    @some_state = {} 
    end 
end 

class MySingletonClass < MyClass 
    include Singleton 
end 

...但我在寻找一个更好的办法喽。

我的问题的一部分是我使用JRuby并挂接到Java系统首选项,其中是全局的。其余的我认为我可以重构。

7
# singleton_spec.rb 
require "singleton" 

class Global 
    include Singleton 

    def initialize 
    puts "Initializing" 
    end 
end 

describe Global do 
    before do 
    Singleton.__init__(Global) 
    end 

    it "test1" do 
    Global.instance 
    end 

    it "test2" do 
    Global.instance 
    end 
end 


% rspec singleton_spec.rb -fd 
Global 
Initializing 
    test1 
Initializing 
    test2 
+0

关键是“Singleton .__ init __(Global)”。那钉为我。谢谢。 – iHiD 2013-10-20 00:22:46

+0

问题是,这会重置单身人士。如果您的应用的任何其他部分依赖于先前设置的全局状态,则会中断。例如,如果您使用的是注册表模式,那么如何测试注册表而不破坏使用注册表的组件的测试? – 2014-05-28 01:44:46

14

辛格尔顿类基本上做到这一点

def self.instance 
    @instance ||= new 
end 

private_class_method :new 

所以,你可以通过调用使用发送

let(:instance) { GlobalClass.send(:new) } 

的这样一个很好的好处是,没有新的私有方法完全绕过记忆化全局状态会因您的测试运行而被修改。


可能是一个更好的办法,从this answer

let(:instance) { Class.new(GlobalClass).instance } 

这将创建一个匿名类从GlobalClass继承,所有类级别的实例变量都存储在这则每个后扔掉。测试,保持GlobalClass不变。

+0

这是一个简单直接的解决方案。 – 2014-05-28 02:24:45

+0

谢谢,这是非常有帮助的。 – chrisco 2015-09-13 12:41:17