Obecnie zastanawiam się, jak napisać testy, które sprawdzą, czy dany fragment kodu wpadł w panikę? Wiem, że Go używa recover
do łapania paniki, ale w przeciwieństwie do, powiedzmy, kodu Java, nie możesz tak naprawdę określić, który kod powinien zostać pominięty w przypadku paniki lub co masz. Więc jeśli mam funkcję:
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")
}
Naprawdę nie mogę powiedzieć, czy OtherFunctionThatPanics
spanikowaliśmy i doszliśmy do siebie, czy też funkcja w ogóle nie panikowała. Jak określić, który kod ma zostać pominięty, jeśli nie ma paniki, i który kod wykonać w przypadku wystąpienia paniki? Jak mogę sprawdzić, czy była jakaś panika, po której wyleczyliśmy się?
r := recover(); r == nil
a nie tylkorecover() == nil
?Jeśli używasz zeznawaj / potwierdzaj , to jest to jeden wiersz:
func TestOtherFunctionThatPanics(t *testing.T) { assert.Panics(t, OtherFunctionThatPanics, "The code did not panic") }
Lub, jeśli masz
OtherFunctionThatPanics
podpis inny niżfunc()
:func TestOtherFunctionThatPanics(t *testing.T) { assert.Panics(t, func() { OtherFunctionThatPanics(arg) }, "The code did not panic") }
Jeśli jeszcze nie próbowałeś zeznawać, sprawdź także zeznawaj / mock . Super proste twierdzenia i kpiny.
źródło
Podczas przeglądania wielu przypadków testowych wybrałbym coś takiego:
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) } }) } }
Idź na plac zabaw
źródło
Kiedy chcesz sprawdzić treść paniki, możesz wpisać odzyskaną wartość:
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) }
Jeśli testowany kod nie wywołuje paniki LUB paniki z błędem LUB paniki z komunikatem o błędzie, którego oczekujesz, test się nie powiedzie (a tego chcesz).
źródło
W Twoim przypadku możesz:
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 // .... } }
Jako ogólna funkcja routera antypanicznego działa również:
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") }, ) }
źródło
Zwięzły sposób
Dla mnie poniższe rozwiązanie jest łatwe do odczytania i pokazuje naturalny przepływ kodu testowanego kodu.
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") }
Aby uzyskać bardziej ogólne rozwiązanie, możesz to również zrobić w następujący sposób:
func TestPanic(t *testing.T) { shouldPanic(t, OtherFunctionThatPanics) } func shouldPanic(t *testing.T, f func()) { defer func() { recover() }() f() t.Errorf("should have panicked") }
źródło
Możesz sprawdzić, która funkcja spowodowała panikę, wprowadzając komunikat panika
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
źródło