2009-12-15 77 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