2009-09-30 5 views
17

Ich mag die einige Unit-Tests schreibe folgendes:Ausgang suppresing mit Ruby zu trösten

def executing_a_signal 
    a_method(a_signal.new, a_model, a_helper); 
    assert_equal(new_state, a_model.state) 
end 

Die Tests funktionieren, aber die Methode, die kurz vor der Behauptung läuft die Logik druckt verschiedene Meldungen auf der Konsole auszuführen hauptsächlich über puts.

Gibt es eine schnelle, vielleicht eingebaute Möglichkeit, diese Ausgabe auf der Konsole zu unterdrücken? Ich interessiere mich nur für den endgültigen Effekt der Methode auf das Modellobjekt, und um die Konsole im Grunde sauber zu halten, hoffte ich, einen Weg zu finden, einfach alle Ausgaben auf der Konsole zu verhindern, ohne sie neu zu schreiben oder zu kommentieren puts Anweisungen nur für meine Tests.

Es ist definitiv kein kritisches Problem, aber ich würde gerne irgendwelche Gedanken oder Ideen (oder Workarounds) darauf hören.

Antwort

31

ich folgende Schnipsel in Tests verwenden zu erfassen und testen STDOUT

def capture_stdout(&block) 
    original_stdout = $stdout 
    $stdout = fake = StringIO.new 
    begin 
    yield 
    ensure 
    $stdout = original_stdout 
    end 
    fake.string 
end 

Mit dieser Methode würde die oben worden:

def executing_a_signal 
    capture_stdout { a_method(a_signal.new, a_model, a_helper) } 
    assert_equal(new_state, a_model.state) 
end 
+0

Danke, das bedeutet wahrscheinlich, dass es keinen Standard-eingebauten Weg gibt? – denchr

+0

Das funktioniert übrigens sehr schön – denchr

+0

Ziemlich ordentliche Lösung, bravo! – khelll

4

ist es zwei Lösungen: Umleitung wo puts zu (das schreibt Lösung, die von @cldwalker oben angegeben wurde) oder die puts-Methode selbst als No-Op zu überschreiben. (Die Implementierung sollte offensichtlich sein: module Kernel; def puts(*args) end end).

In diesem Fall, was wirklich wäre die beste Lösung ist "hören auf Ihre Tests". Denn oft, wenn etwas schwierig zu testen ist, versuchen Ihre Tests, Ihnen zu sagen, dass etwas mit Ihrem Design nicht stimmt. In diesem Fall rieche ich eine Verletzung des Prinzips der einfachen Verantwortung: Warum muss ein Model-Objekt wissen, wie man in die Konsole schreibt? Seine Verantwortung ist ein Domain-Konzept, nicht protokollieren! Dafür sind Logger-Objekte da! Eine alternative Lösung wäre also, dass das Model-Objekt die Verantwortung für die Protokollierung an ein Logger-Objekt delegiert und die Abhängigkeitsinjektion verwendet, um das Model-Objekt mit einem geeigneten Logger-Objekt zu injizieren. Auf diese Weise können Sie einfach einen gefälschten Logger für den Test injizieren.Hier ein Beispiel:

#!/usr/bin/env ruby 

class SomeModel 
    def initialize(logger=Kernel) @logger = logger end 
    def some_method_that_logs; @logger.puts 'bla' end 
end 

require 'test/unit' 
require 'stringio' 
class TestQuietLogging < Test::Unit::TestCase 
    def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end 
    def teardown; $> = @old_stdout end 

    def test_that_default_logging_is_still_noisy 
    SomeModel.new.some_method_that_logs 

    assert_equal "bla\n", @fake_logdest.string 
    end 

    def test_that_logging_can_be_made_quiet 
    fake_logger = Object.new 
    def fake_logger.puts *args; end 

    SomeModel.new(fake_logger).some_method_that_logs 

    assert_equal '', @fake_logdest.string 
    end 
end 

Zumindest sollte das Model-Objekt nehmen Sie das IO Objekt, dass es - als Argument protokolliert, so dass Sie einfach StringIO.new in sie für den Test injizieren:

#!/usr/bin/env ruby 

class SomeModel 
    def initialize(logdest=$>) @logdest = logdest end 
    def some_method_that_logs; @logdest.puts 'bla' end 
end 

require 'test/unit' 
require 'stringio' 
class TestQuietLogging < Test::Unit::TestCase 
    def setup; @old_stdout, $> = $>, (@fake_logdest = StringIO.new) end 
    def teardown; $> = @old_stdout end 

    def test_that_default_logging_is_still_noisy 
    SomeModel.new.some_method_that_logs 

    assert_equal "bla\n", @fake_logdest.string 
    end 

    def test_that_logging_can_be_made_quiet 
    fake_logdest = (@fake_logdest = StringIO.new) 

    SomeModel.new(fake_logdest).some_method_that_logs 

    assert_equal '', @fake_logdest.string 
    assert_equal "bla\n", fake_logdest.string 
    end 
end 

Wenn Sie noch in der Lage sein wollen, nur puts whatever in Ihrem Modell zu sagen, oder Sie haben Angst, dass jemand vergessen puts auf dem Logger-Objekt aufrufen, können Sie Ihre eigenen bereitstellen (privat) legt Methode:

class SomeModel 
    def initialize(logdest=$>) @logdest = logdest end 
    def some_method_that_logs; puts 'bla' end 
    private 
    def puts(*args) @logdest.puts *args end 
end 
1

reopen '/dev/null'

Eine weitere Möglichkeit ist /dev/null zu Umleitung mit:

STDOUT.reopen('/dev/null', 'w') 
STDERR.reopen('/dev/null', 'w') 

Diese Technik auf WEBrick::Daemon des stdlib verwendet wird (Toggle-Quelle).

Es hat den Vorteil, effizienter als StringIO.new zu sein, da es das stdout auf einem StringIO nicht speichert, aber es ist weniger tragbar.

2

Ein etwas nehmen Reiniger auf @ cldwalker Lösung:

def silenced 
    $stdout = StringIO.new 

    yield 
ensure 
    $stdout = STDOUT 
end 

silenced do 
    something_that_prints 
end 
0

Früher habe ich nur den Code unten am Anfang meiner .rb Datei .. so dass es alle Konsole print-Anweisungen deaktivieren ..

def puts(*args) end