Bagaimana cara menguji kepanikan?


90

Saat ini saya sedang memikirkan bagaimana menulis tes yang memeriksa apakah suatu kode panik? Saya tahu bahwa Go menggunakan recoveruntuk menangkap kepanikan, tetapi tidak seperti kode Java, Anda tidak dapat benar-benar menentukan kode apa yang harus dilewati jika terjadi kepanikan atau apa yang Anda miliki. Jadi jika saya memiliki fungsi:

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

Saya tidak bisa benar-benar tahu apakah OtherFunctionThatPanicspanik dan kami pulih, atau apakah fungsinya tidak panik sama sekali. Bagaimana cara menentukan kode mana yang harus dilewati jika tidak ada kepanikan dan kode mana yang harus dijalankan jika terjadi kepanikan? Bagaimana cara memeriksa apakah ada kepanikan yang kami pulihkan?

Jawaban:


106

testingtidak benar-benar memiliki konsep "sukses", hanya kegagalan. Jadi kode Anda di atas sudah benar. Anda mungkin menemukan gaya ini sedikit lebih jelas, tetapi pada dasarnya sama.

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

Saya biasanya merasa testingcukup lemah. Anda mungkin tertarik dengan mesin pengujian yang lebih bertenaga seperti Ginkgo . Bahkan jika Anda tidak menginginkan sistem Ginkgo lengkap, Anda hanya dapat menggunakan perpustakaan matchernya , Gomega , yang dapat digunakan bersama testing. Gomega termasuk korek api seperti:

Expect(OtherFunctionThatPanics).To(Panic())

Anda juga dapat menyelesaikan pemeriksaan panik menjadi fungsi sederhana:

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

@IgorMikushkin di Go 1.11, menggunakan formulir pertama yang dijelaskan oleh Rob Napier sebenarnya berfungsi untuk liputan.
FGM

Apakah ada alasan Anda menggunakan r := recover(); r == nildan tidak adil recover() == nil?
Duncan Jones

@DuncanJones Tidak dalam kasus ini. Ini adalah pola Go yang sangat khas untuk membuat kesalahan tersedia di blok, jadi mungkin OP menulisnya seperti itu (dan saya membawa kodenya ke depan), tetapi sebenarnya tidak digunakan dalam kasus ini.
Rob Napier

44

Jika Anda menggunakan testify / assert , maka itu satu baris:

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

Atau, jika Anda OtherFunctionThatPanicsmemiliki tanda tangan selain func():

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

Jika Anda belum mencoba bersaksi, lihat juga testify / mock . Pernyataan dan ejekan super sederhana.


7

Saat mengulang beberapa kasus uji, saya akan melakukan sesuatu seperti ini:

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

Pergilah bermain


4

Saat Anda perlu memeriksa konten kepanikan, Anda dapat mengetikkan nilai yang dipulihkan:

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

Jika kode yang Anda uji tidak panik ATAU panik dengan kesalahan ATAU panik dengan pesan kesalahan yang Anda harapkan, pengujian akan gagal (yang Anda inginkan).


1
Lebih baik untuk mengetik-menegaskan pada jenis kesalahan tertentu (misalnya os.SyscallError) daripada membandingkan pesan kesalahan, yang dapat berubah (misalnya) dari satu rilis Go ke rilis berikutnya.
Michael

+ Michael Aug, itu mungkin pendekatan yang lebih baik, karena saat ada tipe tertentu yang tersedia.
joonas.fi

3

Dalam kasus Anda, Anda dapat melakukan:

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

Sebagai fungsi router panik generik, ini juga akan berfungsi:

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

3

Cara Ringkas

Bagi saya, solusi di bawah ini mudah dibaca dan menunjukkan aliran kode alami dari kode yang diuji.

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

Untuk solusi yang lebih umum, Anda juga dapat melakukannya seperti ini:

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

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

0

Anda dapat menguji fungsi mana yang panik dengan memberikan masukan kepada panik

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

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.