2014-10-06 9 views
19

Vor diesem CodeWie os.exit Szenarien in Go

func doomed() { 
    os.Exit(1) 
} 

testen Wie richtig, dass ich testen Aufruf dieser Funktion in einem exist führt mit go test? Dies muss innerhalb einer Reihe von Tests erfolgen, mit anderen Worten, der Anruf os.Exit() kann die anderen Tests nicht beeinflussen und sollte abgefangen werden.

+0

Natürlich ist das keine direkte Antwort auf die Frage, und deshalb schreibe ich es nicht als eine, aber generell: Vermeiden Sie es, Code wie diesen zu schreiben. Wenn du am Ende der Welt nur "Exit" ("main"), [wie dieses Muster] (http://stackoverflow.com/a/18969976/455009) verlässt, dann wirst du nicht daran festhalten, solche zu schreiben schmerzhafte Tests als die (gut) akzeptierte Lösung hier.Ich erkenne voll und ganz an, dass Sie vielleicht den Code von jemand anderem getestet haben, den Sie nicht einfach umgestalten könnten, aber ich hoffe nur, dass der Rat für zukünftige Leser hilfreich ist ... – ches

+0

Wenn Sie diesem Muster folgen und Gomega verwenden, hat es [ziemlich cool 'Gexec'-Paket] (http://onsi.github.io/gomega/#gexec-testing-external-processes), das zum Testen der Ergebnisse von ausführbaren Dateien in einer Black-Box-Art geeignet ist. – ches

Antwort

1

Sie können nicht, müssen Sie exec.Command verwenden und den zurückgegebenen Wert testen.

+0

Könnten Sie bitte einen Code und ein Testbeispiel hinzufügen? – 030

6

Ich glaube nicht, dass Sie die tatsächliche os.Exit ohne Simulation von außen (mit exec.Command) Prozess testen können.

Das heißt, könnten Sie in der Lage sein, um Ihr Ziel zu erreichen, indem eine Schnittstelle oder Funktionstyp erstellen und dann eine noop Implementierung in Ihren Tests verwenden:

Go Playground

package main 

import "os" 
import "fmt" 

type exiter func (code int) 

func main() { 
    doExit(func(code int){}) 
    fmt.Println("got here") 
    doExit(func(code int){ os.Exit(code)}) 
} 

func doExit(exit exiter) { 
    exit(1) 
} 
+1

Könnten Sie den Test bitte auch hinzufügen? – 030

0

-Code für die Prüfung:

package main 
import "os" 

var my_private_exit_function func(code int) = os.Exit 

func main() { 
    MyAbstractFunctionAndExit(1) 
} 

func MyAbstractFunctionAndExit(exit int) { 
    my_private_exit_function(exit) 
} 

Testing Code:

package main 

import (
    "os" 
    "testing" 
) 

func TestMyAbstractFunctionAndExit(t *testing.T) { 
    var ok bool = false // The default value can be omitted :) 

    // Prepare testing 
    my_private_exit_function = func(c int) { 
     ok = true 
    } 
    // Run function 
    MyAbstractFunctionAndExit(1) 
    // Check 
    if ok == false { 
     t.Errorf("Error in AbstractFunction()") 
    } 
    // Restore if need 
    my_private_exit_function = os.Exit 
} 
25

Es gibt eine presentation von Andrew Gerrand (eines der Kernmitglieder des Go-Teams), wo er zeigt, wie es geht.

eine Funktion (in main.go)

package main 

import (
    "fmt" 
    "os" 
) 

func Crasher() { 
    fmt.Println("Going down in flames!") 
    os.Exit(1) 
} 

hier Bedenkt man, wie man es (durch main_test.go) prüfen würde:

package main 

import (
    "os" 
    "os/exec" 
    "testing" 
) 

func TestCrasher(t *testing.T) { 
    if os.Getenv("BE_CRASHER") == "1" { 
     Crasher() 
     return 
    } 
    cmd := exec.Command(os.Args[0], "-test.run=TestCrasher") 
    cmd.Env = append(os.Environ(), "BE_CRASHER=1") 
    err := cmd.Run() 
    if e, ok := err.(*exec.ExitError); ok && !e.Success() { 
     return 
    } 
    t.Fatalf("process ran with err %v, want exit status 1", err) 
} 

der Code Was tut, ist go test wieder in einem separaten Prozess aufrufen durch exec.Command , Begrenzung der Ausführung auf den TestCrasher Test (über den -test.run=TestCrasher Schalter). Es gibt auch ein Flag über eine Umgebungsvariable (BE_CRASHER=1) weiter, die der zweite Aufruf überprüft und, falls gesetzt, das zu testende System aufruft, um unmittelbar danach zurückzukehren, um zu verhindern, dass es in eine Endlosschleife läuft. Daher werden wir in unsere ursprüngliche Call-Site zurückversetzt und können nun den tatsächlichen Exit-Code validieren.

Quelle: Slide 23 der Präsentation von Andrew. Die zweite Folie enthält auch einen Link zu der presentation's video. Er spricht über subprocess Tests bei 47:09

+0

Ausführen der Testergebnisse in: 'Prozess mit err exec lautete: "cmd": ausführbare Datei nicht in $ PATH gefunden, will Exit-Status 1 ' – 030

+0

@Alfred Haben halten Sie die Implementierung und Tests in separaten Dateien, zum Beispiel' main. go' und 'main_test.go' jeweils? Ich habe meine Antwort geändert und überprüft, ob sie auf meinem Computer funktioniert. –

+0

Ja. Sie sind getrennt. Könnte es sein, dass etwas mit den Umgebungsvariablen "go env" nicht stimmt? Ich benutze immer 'go test' zum Testen und ich habe mehrere Tests erstellt, um auch andere Dateien zu testen. – 030

4

ich tun dies, indem bouk/monkey mit:

func TestDoomed(t *testing.T) { 
    fakeExit := func(int) { 
    panic("os.Exit called")  
    } 
    patch := monkey.Patch(os.Exit, fakeExit) 
    defer patch.Unpatch() 
    assert.PanicsWithValue(t, "os.Exit called", doomed, "os.Exit was not called") 
} 

Affe ist super-leistungsfähige, wenn es um diese Art von Arbeit kommt, und zur Fehlerinjektion und andere schwierige Aufgaben. Es kommt with some caveats.