จะทดสอบความตื่นตระหนกได้อย่างไร?


91

ฉันกำลังพิจารณาวิธีการเขียนแบบทดสอบเพื่อตรวจสอบว่าโค้ดบางส่วนนั้นตื่นตระหนกหรือไม่? ฉันรู้ว่า Go ใช้recoverเพื่อจับความตื่นตระหนก แต่ไม่เหมือนกับการพูดรหัส Java คุณไม่สามารถระบุได้ว่าควรข้ามรหัสใดในกรณีที่ตกใจหรือคุณมีอะไร ดังนั้นถ้าฉันมีฟังก์ชัน:

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

ฉันไม่สามารถบอกได้จริงๆว่าOtherFunctionThatPanicsตื่นตระหนกและเราฟื้นตัวหรือไม่หรือฟังก์ชั่นไม่ตื่นตระหนกเลย ฉันจะระบุรหัสที่จะข้ามไปได้อย่างไรหากไม่มีความตื่นตระหนกและรหัสใดที่จะดำเนินการหากเกิดความตื่นตระหนก ฉันจะตรวจสอบได้อย่างไรว่าเราหายจากอาการตื่นตระหนกหรือไม่?

คำตอบ:


107

testingไม่ได้มีแนวคิดเรื่อง "ความสำเร็จ" แต่เพียงความล้มเหลว ดังนั้นรหัสของคุณด้านบนจึงถูกต้อง คุณอาจพบว่าสไตล์นี้ชัดเจนขึ้นเล็กน้อย แต่โดยพื้นฐานแล้วมันก็เหมือนกัน

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

ฉันมักจะพบว่าtestingค่อนข้างอ่อนแอ คุณอาจจะสนใจในเครื่องมือทดสอบที่มีประสิทธิภาพมากขึ้นเช่นแปะก๊วย แม้ว่าคุณจะไม่ต้องการให้ระบบแปะก๊วยเต็มคุณสามารถใช้เพียงห้องสมุดจับคู่ของGomegatestingซึ่งสามารถใช้ร่วมกับ Gomega รวมถึง matchers เช่น:

Expect(OtherFunctionThatPanics).To(Panic())

คุณยังสามารถสรุปการตรวจสอบความตื่นตระหนกให้เป็นฟังก์ชันง่ายๆ:

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 ใน Go 1.11 โดยใช้แบบฟอร์มแรกที่ Rob Napier อธิบายไว้ใช้งานได้จริง
FGM

มีเหตุผลใดบ้างที่คุณใช้r := recover(); r == nilไม่ใช่แค่recover() == nil?
Duncan Jones

@DuncanJones ไม่ได้อยู่ในกรณีนี้จริงๆ มันเป็นรูปแบบ Go ทั่วไปที่จะทำให้ข้อผิดพลาดมีอยู่ในบล็อกดังนั้นมันอาจเป็นนิสัยที่ OP จะเขียนแบบนั้น (และฉันก็นำโค้ดของเขาไปข้างหน้า) แต่ก็ไม่ได้ใช้จริงในกรณีนี้
Rob Napier

45

หากคุณใช้คำพยาน / ยืนยันแสดงว่าเป็นหนึ่งซับ:

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

หรือหากคุณOtherFunctionThatPanicsมีลายเซ็นนอกเหนือจากfunc():

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

หากคุณยังไม่ได้ลองเป็นพยานให้ตรวจสอบการเป็นพยาน / เยาะเย้ยด้วย การยืนยันและล้อเลียนที่เรียบง่ายสุด ๆ


7

เมื่อวนลูปในกรณีทดสอบหลาย ๆ กรณีฉันจะทำสิ่งนี้:

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

ไปสนามเด็กเล่น


5

วิธีที่ชัดเจน

สำหรับฉันวิธีแก้ปัญหาด้านล่างอ่านง่ายและแสดงให้ผู้ดูแลเห็นการไหลของรหัสธรรมชาติภายใต้การทดสอบ

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

สำหรับวิธีแก้ปัญหาทั่วไปคุณสามารถทำได้ดังนี้:

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

เมื่อคุณต้องการตรวจสอบเนื้อหาของความตื่นตระหนกคุณสามารถพิมพ์ค่าที่กู้คืนได้:

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

หากรหัสที่คุณกำลังทดสอบไม่ตื่นตระหนกหรือตื่นตระหนกด้วยข้อผิดพลาดหรือตื่นตระหนกกับข้อความแสดงข้อผิดพลาดที่คุณคาดหวังการทดสอบจะล้มเหลว (ซึ่งเป็นสิ่งที่คุณต้องการ)


1
การพิมพ์ยืนยันประเภทข้อผิดพลาดเฉพาะ (เช่น os.SyscallError) มีประสิทธิภาพมากกว่าการเปรียบเทียบข้อความแสดงข้อผิดพลาดซึ่งสามารถเปลี่ยนแปลง (เช่น) จากรุ่น Go หนึ่งไปยังรุ่นถัดไป
Michael

+ Michael Aug นั่นน่าจะเป็นแนวทางที่ดีกว่าสำหรับเมื่อมีประเภทที่เฉพาะเจาะจง
joonas.fi

3

ในกรณีของคุณคุณสามารถทำได้:

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

เป็นฟังก์ชั่นเราเตอร์ตกใจทั่วไปสิ่งนี้จะใช้งานได้เช่นกัน:

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

คุณสามารถทดสอบฟังก์ชั่นที่ตื่นตระหนกได้โดยให้อินพุตตกใจ

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.