Làm thế nào để kiểm tra hoảng loạn?


90

Tôi hiện đang cân nhắc cách viết các bài kiểm tra để kiểm tra xem một đoạn mã nhất định có bị hoảng loạn hay không? Tôi biết rằng Go sử dụng recoverđể bắt hoảng loạn, nhưng không giống như mã Java, bạn thực sự không thể chỉ định mã nào nên được bỏ qua trong trường hợp hoảng sợ hoặc bạn có gì. Vì vậy, nếu tôi có một chức năng:

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

Tôi thực sự không thể biết liệu OtherFunctionThatPanicshoảng loạn và chúng tôi đã hồi phục, hay nếu chức năng không hoảng loạn chút nào. Làm cách nào để chỉ định mã nào cần bỏ qua nếu không có hoảng sợ và mã nào để thực thi nếu có hoảng sợ? Làm thế nào tôi có thể kiểm tra xem chúng tôi đã khôi phục được một số hoảng sợ hay chưa?

Câu trả lời:


106

testingkhông thực sự có khái niệm "thành công", chỉ thất bại. Vì vậy, mã của bạn ở trên là đúng. Bạn có thể thấy phong cách này rõ ràng hơn một chút, nhưng về cơ bản thì nó giống nhau.

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

Tôi thường thấy testinglà khá yếu. Bạn có thể quan tâm đến các công cụ thử nghiệm mạnh mẽ hơn như Ginkgo . Ngay cả khi bạn không muốn có hệ thống Ginkgo đầy đủ, bạn có thể chỉ sử dụng thư viện đối sánh của nó, Gômga , có thể được sử dụng cùng với testing. Gmega bao gồm các đối sánh như:

Expect(OtherFunctionThatPanics).To(Panic())

Bạn cũng có thể kết hợp kiểm tra hoảng sợ thành một chức năng đơn giản:

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 trong Go 1.11, sử dụng biểu mẫu đầu tiên được Rob Napier mô tả thực sự hoạt động cho phạm vi bảo hiểm.
FGM

Có lý do nào bạn sử dụng r := recover(); r == nilvà không chỉ recover() == nil?
Duncan Jones

@DuncanJones Không thực sự trong trường hợp này. Đó là một mô hình cờ vây thực sự điển hình để tạo ra lỗi có sẵn trong khối, vì vậy OP có thói quen viết nó theo cách đó (và tôi đã đưa mã của anh ấy về phía trước), nhưng nó không thực sự được sử dụng trong trường hợp này.
Rob Napier

43

Nếu bạn sử dụng chứng thực / khẳng định , thì đó là một lớp lót:

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

Hoặc, nếu OtherFunctionThatPanicschữ ký của bạn không phải là func():

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

Nếu bạn chưa thử làm chứng, thì cũng hãy xem làm chứng / mô phỏng . Khẳng định và chế giễu siêu đơn giản.


7

Khi lặp qua nhiều trường hợp thử nghiệm, tôi sẽ làm như sau:

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

Đi chơi


4

Khi bạn cần kiểm tra nội dung của hoảng loạn, bạn có thể nhập giá trị khôi phục:

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

Nếu mã bạn đang kiểm tra không hoảng sợ HOẶC hoảng sợ với lỗi HOẶC hoảng sợ với thông báo lỗi bạn mong đợi, kiểm tra sẽ không thành công (đó là điều bạn muốn).


1
Việc xác nhận kiểu trên một loại lỗi cụ thể (ví dụ: os.SyscallError) sẽ mạnh mẽ hơn là so sánh các thông báo lỗi, thông báo này có thể thay đổi (ví dụ) từ bản phát hành Go sang bản tiếp theo.
Michael

+ Michael Aug, đó có lẽ là cách tiếp cận tốt hơn, khi có một loại cụ thể.
joonas.fi

3

Trong trường hợp của bạn, bạn có thể làm:

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

Là một chức năng chung của bộ định tuyến hoảng loạn, chức năng này cũng sẽ hoạt động:

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

Cách Succinct

Đối với tôi, giải pháp dưới đây rất dễ đọc và hiển thị cho bạn luồng mã tự nhiên của mã đang được thử nghiệm.

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

Để có giải pháp tổng quát hơn, bạn cũng có thể thực hiện như sau:

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

Bạn có thể kiểm tra chức năng nào bị hoảng sợ bằng cách nhập đầu vào cho chức năng hoảng sợ

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

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.