Actualmente estoy pensando en cómo escribir pruebas que comprueben si un fragmento de código dado entró en pánico. Sé que Go recover
suele atrapar el pánico, pero a diferencia de, digamos, el código Java, realmente no se puede especificar qué código se debe omitir en caso de pánico o lo que sea. Entonces, si tengo una función:
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")
}
Realmente no puedo decir si OtherFunctionThatPanics
entró en pánico y nos recuperamos, o si la función no entró en pánico en absoluto. ¿Cómo especifico qué código omitir si no hay pánico y qué código ejecutar si hay pánico? ¿Cómo puedo comprobar si hubo algún pánico del que nos recuperamos?
r := recover(); r == nil
y no solorecover() == nil
?Si usa testificar / afirmar , entonces es una línea:
func TestOtherFunctionThatPanics(t *testing.T) { assert.Panics(t, OtherFunctionThatPanics, "The code did not panic") }
O, si
OtherFunctionThatPanics
tiene una firma que no seafunc()
:func TestOtherFunctionThatPanics(t *testing.T) { assert.Panics(t, func() { OtherFunctionThatPanics(arg) }, "The code did not panic") }
Si aún no ha intentado testificar, consulte también testificar / simular . Afirmaciones y burlas súper simples.
fuente
Al recorrer varios casos de prueba, optaría por algo como esto:
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) } }) } }
Ir al patio de recreo
fuente
Cuando necesite verificar el contenido del pánico, puede encasillar el valor recuperado:
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) }
Si el código que está probando no entra en pánico O entra en pánico con un error O entra en pánico con el mensaje de error que espera, la prueba fallará (que es lo que querría).
fuente
En tu caso puedes hacer:
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 // .... } }
Como función genérica de enrutador de pánico, esto también funcionará:
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") }, ) }
fuente
Manera sucinta
Para mí, la solución a continuación es fácil de leer y le muestra el flujo de código natural del código bajo prueba.
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") }
Para una solución más general, también puede hacerlo así:
func TestPanic(t *testing.T) { shouldPanic(t, OtherFunctionThatPanics) } func shouldPanic(t *testing.T, f func()) { defer func() { recover() }() f() t.Errorf("should have panicked") }
fuente
Puede probar qué función sufrió pánico dando una entrada al pánico
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
fuente