Panik nasıl test edilir?


92

Şu anda, belirli bir kod parçasının panik yapıp yapmadığını kontrol eden testlerin nasıl yazılacağını düşünüyorum? Go'nun recoverpaniği yakalamak için kullandığını biliyorum , ancak örneğin Java kodunun aksine, panik durumunda veya neye sahip olduğunuzda hangi kodun atlanması gerektiğini gerçekten belirleyemezsiniz. Yani bir işlevim varsa:

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")
}

OtherFunctionThatPanicsPaniklediğimizi ve iyileştiğimizi veya işlevin hiç panik yapmadığını gerçekten söyleyemem . Panik yoksa hangi kodu atlayacağımı ve panik olduğunda hangi kodu çalıştıracağımı nasıl belirleyebilirim? Kurtulduğumuz bir panik olup olmadığını nasıl kontrol edebilirim?

Yanıtlar:


107

testinggerçekten "başarı" kavramına sahip değil, sadece başarısızlık. Yani yukarıdaki kodunuz doğru. Bu stili biraz daha net bulabilirsiniz, ancak temelde aynı şeydir.

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()
}

Genelde testingoldukça zayıf buluyorum . Ginkgo gibi daha güçlü test motorlarıyla ilgilenebilirsiniz . Tam Ginkgo sistemini istemeseniz bile, sadece eşleştirici kitaplığı olan Gomega'yı kullanabilirsiniz testing. Gomega, aşağıdaki gibi eşleştiricileri içerir:

Expect(OtherFunctionThatPanics).To(Panic())

Ayrıca panik kontrolünü basit bir işleve dönüştürebilirsiniz:

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()
}

Go 1.11'de @IgorMikushkin, Rob Napier tarafından açıklanan ilk formu kullanarak aslında kapsama alanı için çalışıyor.
FGM

r := recover(); r == nilSadece kullanmanın bir nedeni var mı recover() == nil?
Duncan Jones

@DuncanJones Bu durumda pek değil. Hatayı blokta kullanılabilir kılmak için gerçekten tipik bir Go kalıbıdır, bu nedenle OP'nin bunu bu şekilde yazması muhtemelen bir alışkanlıktı (ve kodunu ileri götürdüm), ancak bu durumda aslında kullanılmıyor.
Rob Napier

46

Eğer tanıklık / assert kullanıyorsanız , o zaman tek satırlıktır:

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

Veya, aşağıdakilerden OtherFunctionThatPanicsfarklı bir imzanız varsa func():

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

Henüz tanıklık yapmayı denemediyseniz, ayrıca tanıklık / alay konusuna da göz atın . Süper basit iddialar ve alaylar.


7

Birden fazla test senaryosu üzerinde döngü oluştururken şöyle bir şey yapardım:

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)
            }
        })
    }
}

Oyun alanına git


6

Kısa Yol

Bana göre aşağıdaki çözümün okunması kolaydır ve bir bakıcıya test edilen doğal kod akışını gösterir.

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")
}

Daha genel bir çözüm için bunu şu şekilde de yapabilirsiniz:

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

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

4

Panik içeriğini kontrol etmeniz gerektiğinde, kurtarılan değeri yazabilirsiniz:

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)
}

Test ettiğiniz kod panik yapmaz VEYA bir hata ile paniğe kapılmaz VEYA olmasını beklediğiniz hata mesajıyla paniğe kapılmazsa, test başarısız olur (istediğiniz şey budur).


1
Belirli bir hata türüne (örn. Os.SyscallError) yazıp onaylamak, bir Go sürümünden diğerine değişebilen (örneğin) hata mesajlarını karşılaştırmaktan daha sağlamdır.
Michael

+ Michael Aug, belirli bir tür mevcut olduğunda muhtemelen daha iyi yaklaşım budur.
joonas.fi

3

Sizin durumunuzda şunları yapabilirsiniz:

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
        // ....
    }
}

Genel bir panik yönlendirici işlevi olarak bu da işe yarayacaktır:

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")
        },
    )
}

0

Panik olarak bir girdi vererek hangi işlevi paniklediğini test edebilirsiniz.

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

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.