Wie teste ich Panik?

87

Ich überlege gerade, wie ich Tests schreiben soll, die prüfen, ob ein bestimmter Code in Panik gerät. Ich weiß, dass Go recoverPanik auslöst, aber im Gegensatz zu Java-Code können Sie nicht genau angeben, welcher Code im Falle einer Panik übersprungen werden soll oder was Sie haben. Also, wenn ich eine Funktion habe:

func f(t *testing.T) {
    defer func() {
        if r := recover(); r != nil {
            fmt.Println("Recovered in f", r)
        }
    }()
    OtherFunctionThatPanics()
    t.Errorf("The code did not panic")
}

Ich kann nicht wirklich sagen, ob OtherFunctionThatPanicswir in Panik geraten sind und uns erholt haben oder ob die Funktion überhaupt nicht in Panik geraten ist. Wie gebe ich an, welcher Code übersprungen werden soll, wenn keine Panik vorliegt, und welcher Code ausgeführt werden soll, wenn eine Panik auftritt? Wie kann ich überprüfen, ob es eine Panik gab, von der wir uns erholt haben?

ThePiachu
quelle

Antworten:

104

testinghat nicht wirklich das Konzept von "Erfolg", nur Misserfolg. Ihr Code oben ist also ungefähr richtig. Sie werden diesen Stil vielleicht etwas klarer finden, aber es ist im Grunde das Gleiche.

func TestPanic(t *testing.T) {
    defer func() {
        if r := recover(); r == nil {
            t.Errorf("The code did not panic")
        }
    }()

    // The following is the code under test
    OtherFunctionThatPanics()
}

Ich finde testinges im Allgemeinen ziemlich schwach. Sie könnten an leistungsstärkeren Testmotoren wie Ginkgo interessiert sein . Auch wenn Sie nicht das vollständige Ginkgo-System möchten, können Sie nur die Matcher-Bibliothek Gomega verwenden , die zusammen mit verwendet werden kann testing. Gomega enthält Matcher wie:

Expect(OtherFunctionThatPanics).To(Panic())

Sie können die Panikprüfung auch in eine einfache Funktion einbinden:

func TestPanic(t *testing.T) {
    assertPanic(t, OtherFunctionThatPanics)
}

func assertPanic(t *testing.T, f func()) {
    defer func() {
        if r := recover(); r == nil {
            t.Errorf("The code did not panic")
        }
    }()
    f()
}
Rob Napier
quelle
@IgorMikushkin in Go 1.11 verwendet die erste von Rob Napier beschriebene Form tatsächlich für die Berichterstattung.
FGM
Gibt es einen Grund, den Sie verwenden r := recover(); r == nilund nicht nur recover() == nil?
Duncan Jones
@ DuncanJones Nicht wirklich in diesem Fall. Es ist ein wirklich typisches Go-Muster, um den Fehler im Block verfügbar zu machen, daher war es wahrscheinlich Gewohnheit des OP, ihn so zu schreiben (und ich habe seinen Code vorgebracht), aber er wird in diesem Fall nicht wirklich verwendet.
Rob Napier
39

Wenn Sie testify / assert verwenden , handelt es sich um einen Einzeiler:

func TestOtherFunctionThatPanics(t *testing.T) {
  assert.Panics(t, OtherFunctionThatPanics, "The code did not panic")
}

Oder wenn Sie OtherFunctionThatPanicseine andere Unterschrift haben als func():

func TestOtherFunctionThatPanics(t *testing.T) {
  assert.Panics(t, func() { OtherFunctionThatPanics(arg) }, "The code did not panic")
}

Wenn Sie noch nicht versucht haben, Zeugnis zu geben, lesen Sie auch Zeugnis / Verspottung . Super einfache Behauptungen und Verspottungen.

Jacob Marmor
quelle
6

Beim Durchlaufen mehrerer Testfälle würde ich Folgendes wählen:

package main

import (
    "reflect"
    "testing"
)


func TestYourFunc(t *testing.T) {
    type args struct {
        arg1 int
        arg2 int
        arg3 int
    }
    tests := []struct {
        name      string
        args      args
        want      []int
        wantErr   bool
        wantPanic bool
    }{
        //TODO: write test cases
    }
    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            defer func() {
                r := recover()
                if (r != nil) != tt.wantPanic {
                    t.Errorf("SequenceInt() recover = %v, wantPanic = %v", r, tt.wantPanic)
                }
            }()
            got, err := YourFunc(tt.args.arg1, tt.args.arg2, tt.args.arg3)
            if (err != nil) != tt.wantErr {
                t.Errorf("YourFunc() error = %v, wantErr %v", err, tt.wantErr)
                return
            }
            if !reflect.DeepEqual(got, tt.want) {
                t.Errorf("YourFunc() = %v, want %v", got, tt.want)
            }
        })
    }
}

Geh auf den Spielplatz

Aleh
quelle
4

Wenn Sie den Inhalt der Panik überprüfen müssen, können Sie den wiederhergestellten Wert typisieren:

func TestIsAheadComparedToPanicsWithDifferingStreams(t *testing.T) {
    defer func() {
        err := recover().(error)

        if err.Error() != "Cursor: cannot compare cursors from different streams" {
            t.Fatalf("Wrong panic message: %s", err.Error())
        }
    }()

    c1 := CursorFromserializedMust("/foo:0:0")
    c2 := CursorFromserializedMust("/bar:0:0")

    // must panic
    c1.IsAheadComparedTo(c2)
}

Wenn der Code, den Sie testen, nicht in Panik gerät ODER mit einem Fehler in Panik gerät ODER mit der von Ihnen erwarteten Fehlermeldung in Panik gerät, schlägt der Test fehl (was Sie möchten).

joonas.fi
quelle
1
Es ist robuster, einen bestimmten Fehlertyp (z. B. os.SyscallError) zu typisieren, als Fehlermeldungen zu vergleichen, die sich (z. B.) von einer Go-Version zur nächsten ändern können.
Michael
+ Michael Aug, das ist wahrscheinlich der bessere Ansatz, wenn ein bestimmter Typ verfügbar ist.
joonas.fi
3

In Ihrem Fall können Sie Folgendes tun:

func f(t *testing.T) {
    recovered := func() (r bool) {
        defer func() {
            if r := recover(); r != nil {
                r = true
            }
        }()
        OtherFunctionThatPanics()
        // NOT BE EXECUTED IF PANICS
        // ....
    }
    if ! recovered() {
        t.Errorf("The code did not panic")

        // EXECUTED IF PANICS
        // ....
    }
}

Als generische Panik-Router-Funktion funktioniert dies auch:

https://github.com/7d4b9/recover

package recover

func Recovered(IfPanic, Else func(), Then func(recover interface{})) (recoverElse interface{}) {
    defer func() {
        if r := recover(); r != nil {
            {
                // EXECUTED IF PANICS
                if Then != nil {
                    Then(r)
                }
            }
        }
    }()

    IfPanic()

    {
        // NOT BE EXECUTED IF PANICS
        if Else != nil {
            defer func() {
                recoverElse = recover()
            }()
            Else()
        }
    }
    return
}

var testError = errors.New("expected error")

func TestRecover(t *testing.T) {
    Recovered(
        func() {
            panic(testError)
        },
        func() {
            t.Errorf("The code did not panic")
        },
        func(r interface{}) {
            if err := r.(error); err != nil {
                assert.Error(t, testError, err)
                return
            }
            t.Errorf("The code did an unexpected panic")
        },
    )
}
David B.
quelle
1

Prägnanter Weg

Für mich ist die folgende Lösung leicht zu lesen und zeigt Ihnen den natürlichen Codefluss des zu testenden Codes.

func TestPanic(t *testing.T) {
    // No need to check whether `recover()` is nil. Just turn off the panic.
    defer func() { recover() }()

    OtherFunctionThatPanics()

    // Never reaches here if `OtherFunctionThatPanics` panics.
    t.Errorf("did not panic")
}

Für eine allgemeinere Lösung können Sie dies auch folgendermaßen tun:

func TestPanic(t *testing.T) {
    shouldPanic(t, OtherFunctionThatPanics)
}

func shouldPanic(t *testing.T, f func()) {
    defer func() { recover() }()
    f()
    t.Errorf("should have panicked")
}
Inanc Gumus
quelle
0

Sie können testen, welche Funktion in Panik geraten ist, indem Sie Panik eingeben

package main

import "fmt"

func explode() {
    // Cause a panic.
    panic("WRONG")
}

func explode1() {
    // Cause a panic.
    panic("WRONG1")
}

func main() {
    // Handle errors in defer func with recover.
    defer func() {
        if r := recover(); r != nil {
            var ok bool
            err, ok := r.(error)
            if !ok {
                err = fmt.Errorf("pkg: %v", r)
                fmt.Println(err)
            }
        }

    }()
    // These causes an error. change between these
    explode()
    //explode1()

    fmt.Println("Everything fine")

}

http://play.golang.org/p/ORWBqmPSVA

Thellimist
quelle