패닉을 테스트하는 방법?


90

나는 현재 주어진 코드가 당황했는지 확인하는 테스트를 작성하는 방법을 고민하고 있습니다. 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당황하고 우리가 회복 했는지 또는 기능이 전혀 당황하지 않았 는지 정말로 말할 수 없습니다 . 패닉이없는 경우 건너 뛸 코드와 패닉이있는 경우 실행할 코드를 어떻게 지정합니까? 회복 된 공황이 있었는지 어떻게 확인할 수 있습니까?

답변:


106

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상당히 약하다는 것을 알게 됩니다. Ginkgo 와 같은 더 강력한 테스트 엔진에 관심이있을 수 있습니다 . 전체 Ginkgo 시스템을 원하지 않더라도 .NET 과 함께 사용할 수있는 일치 라이브러리 인 Gomega 만 사용할 수 있습니다 testing. Gomega에는 다음과 같은 매 처가 포함됩니다.

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

Go 1.11의 @IgorMikushkin은 Rob Napier가 설명한 첫 번째 형식을 사용하여 실제로 적용됩니다.
FGM

당신이 사용하는 이유가 r := recover(); r == nil아니라 단지는 recover() == nil?
Duncan Jones

@DuncanJones이 경우에는 그렇지 않습니다. 블록에서 오류를 사용할 수 있도록 만드는 것은 정말 전형적인 Go 패턴이므로 OP가 그렇게 작성하는 것이 습관이었을 것입니다 (그리고 그의 코드를 전달했습니다).
Rob Napier

43

testify / assert 를 사용 하는 경우 한 줄짜리입니다.

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

아직 testify를 시도하지 않았다면 testify / mock을 확인하십시오 . 매우 간단한 주장과 모의.


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

이동 놀이터


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

3

간결한 방법

저에게 아래 솔루션은 읽기 쉽고 테스트중인 코드의 자연스러운 코드 흐름을 보여줍니다.

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

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.