2013-06-06 19 views
8

Gibt es eine Möglichkeit, Variablen zu verfolgen, die bei Verwendung von let erstellt werden?Rspec let() bereinigen

Ich habe eine Reihe von Tests, von denen einige verwenden lassen (: Server) {# blah blah}. Ein Teil des Blahs besteht darin, auf den Start des Servers zu warten, damit er in einem anständigen Zustand ist, bevor er benutzt wird.

Das Problem kommt, wenn ich mit diesem Test fertig bin. Ich möchte den Server mit server.kill() töten. Dies wäre fast perfekt, wenn ich etwas zu den Auswirkungen von

after(:each) { server.kill } 

sagen konnte, aber dies würde den Server erstellen und alle Ressourcen/Zeit verschwenden, es zu schaffen, wenn es referenziert wird, nur um es zu töten sofort, wenn der Server wurde im vorhergehenden Test nicht verwendet. Gibt es eine Möglichkeit, den Server zu überwachen und ihn nur zu bereinigen, wenn er benutzt wurde?

Antwort

3

Was ich tun würde, ist so etwas wie dieses:

describe MyTest do 
    let(:server) { Server.new } 

    context "without server" do 
    ## dont kill the server in here. 
    end 

    context "with server" do 

    before do 
    server 
    end 

    after(:each) { server.kill } 

    it {} 
    it {} 
    end 
end 
+0

Ich könnte etwas ähnliches tun, aber anstatt das let (: server) {} im Block der obersten Ebene describe zu haben, verschiebe es in den Kontext "mit Server", entferne das before (: each) {} und dann das after (: each}} würde funktionieren ... – rynojvr

+0

@MichaelPapile: block wird nur einmal im Kontext aufgerufen, also sollte der after-Block gut funktionieren –

+0

@MichaelPapile: Ich hatte Probleme mit let() vor, und einige der let() s-Effekte zu existieren, aber noch nicht existierend, weil das let(), das es verursacht hat, noch nicht referenziert wurde.Ich benutzte einen Workaround, bevor ich von let!() wusste, in dem ich hatte einen before (: each) -Block, der einfach aus dem Namen des beleidigenden let() bestand, was bedeutet, dass der Kontext von let() sich auf den before (: each) -Block für diesen Test erstreckt und vernünftigerweise das after enthalten sollte (: jeder) Block auch. – rynojvr

4

ich auf ein ähnliches Problem gekommen. Ein einfacher Weg, dies zu lösen, ist eine Instanzvariable in der let-Methode gesetzt, wenn das Objekt zu verfolgen erstellt wurde:

describe MyTest do 

    before(:each) { @created_server = false } 

    let(:server) { 
    @created_server = true 
    Server.new 
    } 

    after(:each) { server.kill if @created_server } 

end 
+0

Ich liebe diese Methode. Danke, dass du es geteilt hast. Ich benutzte es, um einige Modelle zu entfernen, die während der Tests zur db hinzugefügt wurden, aber nach dem Testlauf nicht gelöscht wurden. – labyrinth

2

Dies ist definitiv ein Hack:

describe "cleanup for let" do 
    let(:expensive_object) { 
    ExpensiveObject.new 
    } 
    after(:context) { 
    v = __memoized[:expensive_object] 
    v.close if v 
    } 
end 

ich dachte, dass rspec mußte Speichern Sie diese Lazy-Werte irgendwo die Instanz könnte auf sie zugreifen, und __memoized ist dieser Ort.

mit einem Helfer, wird es ein wenig aufgeräumter:

def cleanup(name, &block) 
    after(:context) do 
    v = __memoized[name] 
    instance_exec(v, &block) if v 
    end 
end 

describe "cleanup for let" do 
    let(:expensive_object) { 
    ExpensiveObject.new 
    } 
    cleanup(:expensive_object) { |v| 
    v.close 
    } 
end 

Es noch Raum für Verbesserungen ist, though. Ich glaube, ich würde lieber nicht zweimal den Namen des Objekts eingeben, so etwas wie dies wäre schöner:

describe "cleanup for let" do 
    let(:expensive_object) { 
    ExpensiveObject.new 
    }.cleanup { |v| 
    v.close 
    } 
end 

Ich bin nicht sicher, was ich tun kann, dass ohne rspec in Stücke zu hacken, aber vielleicht, wenn RSpec sich Säge der Nutzen davon, könnte etwas in Kern getan werden ...

Edit:instance_exec zu verwenden geändert, weil rspec jammern begann, wenn die Dinge aus dem falschen Kontext aufgerufen wurden, und geänderte Bereinigung after(:context), zu sein, weil anscheinend ist das Niveau, auf dem es sich befindet.

0

Schreiben Sie einfach einen kleinen Decorator, der sowohl das explizite als auch das implizite Starten des Servers behandelt und mit dem Sie feststellen können, ob der Server gestartet wurde.

Stellen Sie sich der reale Server sein, der gestartet werden muss:

class ServiceWrapper < BasicObject 
    def initialize(&start_procedure) 
    @start_procedure = start_procedure 
    end 

    def started? 
    [email protected] 
    end 

    def instance 
    @instance ||= @start_procedure.call 
    end 

    alias start instance 

    private 

    def method_missing(method_name, *arguments) 
    instance.public_send(method_name, *arguments) 
    end 

    def respond_to?(method_name) 
    super || instance.respond_to?(method_name) 
    end 
end 

Jetzt können Sie diese wie folgt in Ihren Angaben gelten:

class TheActualServer 
    def initialize 
    puts 'Server starting' 
    end 

    def operation1 
    1 
    end 

    def operation2 
    2 
    end 

    def kill 
    puts 'Server stopped' 
    end 
end 

Der wiederverwendbare Dekorateur könnte wie folgt aussehen :

Wenn eine Methode auf dem Decorator aufgerufen wird, wird es seine eigene IM ausführen Ergänzung, wenn eine existiert.Wenn zum Beispiel #started? aufgerufen wird, wird es antworten, ob der tatsächliche Server gestartet wurde oder nicht. Wenn es keine eigene Implementierung dieser Methode hat, delegiert es den Methodenaufruf an das von ihm zurückgegebene Serverobjekt. Wenn es zu diesem Zeitpunkt keinen Verweis auf eine Instanz des tatsächlichen Servers hat, wird das bereitgestellte start_procedure ausgeführt, um eins zu erhalten und dieses für zukünftige Aufrufe zu notieren.

Wenn Sie alle geschrieben Code in eine server_spec.rb Datei mit dem Namen setzen können Sie dann führen Sie es mit:

rspec server_spec.rb 

Die Ausgabe wird so sein:

something 
Server starting 
Server stopped 
    should eql 1 
Server starting 
Server stopped 
    should eql 2 
    should be a kind of Numeric 
    when server is running 
Server starting 
Server stopped 
    should be a kind of String 
Server starting 
Server stopped 
    should be a kind of Regexp 

Finished in 0.00165 seconds (files took 0.07534 seconds to load) 
5 examples, 0 failures 

Beachten Sie, dass in den Beispielen 1 und 2 werden Methoden auf dem Server aufgerufen, und daher sehen Sie die Ausgabe des Servers, der implizit vom Decorator gestartet wird.

In Beispiel 3 gibt es überhaupt keine Interaktion mit dem Server, daher sehen Sie die Ausgabe des Servers nicht im Protokoll.

Dann gibt es wiederum in Beispiel 4 und 5 keine direkte Interaktion mit dem Serverobjekt im Beispielcode, aber der Server wird explizit durch einen Vorherblock gestartet, der auch in der Ausgabe zu sehen ist.