2014-11-07 8 views
8

Ich habe eine einfache Funktion möchte ich Test:Wie eine Funktion der Ausgabe (stdout/stderr) in Go-Einheit testen Tests

func (t *Thing) print(min_verbosity int, message string) { 
    if t.verbosity >= minv { 
     fmt.Print(message) 
    } 
} 

Aber wie kann ich testen, was die Funktion tatsächlich sendet an die Standardausgabe? Test::Output macht was ich will in Perl. Ich weiß, ich könnte alle meine eigenen Textvorschlag schreiben das gleiche in Gehen Sie zu tun (wie here beschrieben):

orig = os.Stdout 
r,w,_ = os.Pipe() 
thing.print("Some message") 
var buf bytes.Buffer 
io.Copy(&buf, r) 
w.Close() 
os.Stdout = orig 
if(buf.String() != "Some message") { 
    t.Error("Failure!") 
} 

Aber das ist eine Menge zusätzlicher Arbeit für jeden einzelnen Test. Ich hoffe, es gibt einen Standardweg oder vielleicht eine Abstraktionsbibliothek, um damit umzugehen.

Antwort

15

Eine Sache, an die man sich auch erinnert, es gibt nichts, was dich davon abhält, Funktionen zu schreiben, um das Boilerplate zu vermeiden.

Zum Beispiel habe ich eine Befehlszeile-Anwendung, die log verwendet und ich schrieb diese Funktion:

func captureOutput(f func()) string { 
    var buf bytes.Buffer 
    log.SetOutput(&buf) 
    f() 
    log.SetOutput(os.Stderr) 
    return buf.String() 
} 

Dann verwendet es wie folgt aus:

output := captureOutput(func() { 
    client.RemoveCertificate("www.example.com") 
}) 
assert.Equal("removed certificate www.example.com\n", output) 

diese Assertion-Bibliothek verwenden: http://godoc.org/github.com/stretchr/testify/assert.

+0

Dies ist wahrscheinlich, was ich tun werde, da es eine allgemeinere Lösung als die von @ Ainar-G ist, die erfordert, dass ich den gesamten Code kontrolliere, der etwas ausgeben könnte. – Flimzy

9

Sie können eines von zwei Dingen tun. Die erste ist Examples zu verwenden.

Das Paket wird auch ausgeführt und überprüft Beispielcode. Beispielfunktionen können einen abschließenden Zeilenkommentar enthalten, der mit "Ausgabe:" beginnt und mit der Standardausgabe der Funktion verglichen wird, wenn die Tests ausgeführt werden. (. Der Vergleich ignoriert führende und Raumhinter) Dies sind Beispiele für ein Beispiel:

func ExampleHello() { 
     fmt.Println("hello") 
     // Output: hello 
} 

Die zweite (und besser geeignet, IMO) ist fake Funktionen für Ihre IO zu verwenden. In Ihrem Code Sie tun:

var myPrint = fmt.Print 

func (t *Thing) print(min_verbosity int, message string) { 
    if t.verbosity >= minv { 
     myPrint(message) // N.B. 
    } 
} 

Und in Ihren Tests:

func init() { 
    myPrint = fakePrint // fakePrint records everything it's supposed to print. 
} 

func Test... 

Eine weitere Option fmt.Fprintf mit einem io.Writer zu verwenden ist, die os.Stdout in Produktionscode ist, kann aber bytes.Buffer in Tests sagen werden.

+1

Beispiele sind gut für, wenn Sie de haben terministische Ausgabe und fmt.Fprint ist gut für, wenn Sie den Druckcode steuern können (was Sie nicht können, wenn es in einer externen Bibliothek oder etwas ist). Ich denke die meiste Zeit wird eines dieser Dinge wahr sein. Wenn es einen Fall gibt, in dem keine der beiden Optionen zutrifft, wäre eine Option zum Lesen, was in os.Stdout in eine Zeichenfolge geschrieben wird, nett. – voutasaurus

1

Sie könnten eine Rückgabeanweisung zu Ihrer Funktion hinzufügen, um die tatsächlich ausgedruckte Zeichenfolge zurückzugeben.

Jetzt konnte Ihr Test einfach die zurückgegebene Zeichenfolge anhand einer erwarteten Zeichenfolge überprüfen (anstatt des Ausdrucks). Vielleicht ein bisschen mehr in Einklang mit Test Driven Development (TDD).

Und in Ihrem Produktionscode müsste sich nichts ändern, da Sie den Rückgabewert einer Funktion nicht zuweisen müssen, wenn Sie sie nicht benötigen.

0

Ich liebe alle Kommentare hier und wollte meine Lösung für die Handhabung Print Funktionen hinzufügen.Wie Sie sehen können, habe ich einen Rückgabetyp string implementiert, aber Handhabung ist es optional, wie ich es genauso nennen:

PrintWarning("Some warning")

Funktionsdeklaration:

var test = false 

func PrintWarning(format string, values ...interface{}) string { 
    message := fmt.Sprintf("[WARNING] " + format, values...) 

    c := color.New(color.FgYellow) 
    if !test { 
     c.Println(message) 
    } 

    return c.Sprintln(message) 
} 

Test-Deklaration:

func TestPrintWarning(t *testing.T) { 
    test = true 
    result := PrintWarning("Testing warning") 
    assert.Equal(t, result, "[WARNING] Testing warning\n") 
}