How to test panics?

前端 未结 7 474
别跟我提以往
别跟我提以往 2020-12-12 20:19

I\'m currently pondering how to write tests that check if a given piece of code panicked? I know that Go uses recover to catch panics, but unlike say, Java code, you can\'t

相关标签:
7条回答
  • 2020-12-12 20:31

    When looping over multiple test cases I would go for something like this:

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

    Go playground

    0 讨论(0)
  • 2020-12-12 20:32

    When you need to check the content of the panic, you can typecast the recovered value:

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

    If the code you're testing does not panic OR panic with an error OR panic with the error message you expect it to, the test will fail (which is what you'd want).

    0 讨论(0)
  • 2020-12-12 20:33

    Succinct Way

    To me, the solution below is easy to read and shows a maintainer the natural code flow under test.

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

    For a more general solution, you can also do it like this:

    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 讨论(0)
  • 2020-12-12 20:35

    You can test which function paniced by giving panic an input

    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

    0 讨论(0)
  • 2020-12-12 20:48

    In your case you can do:

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

    As a generic panic router function this will also work:

    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 讨论(0)
  • 2020-12-12 20:55

    If you use testify/assert, then it's a one-liner:

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

    Or, if your OtherFunctionThatPanics has a signature other than func():

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

    If you haven't tried testify yet, then also check out testify/mock. Super simple assertions and mocks.

    0 讨论(0)
提交回复
热议问题