GoConvey是一款优秀的Golang测试框架,可以快速优雅的开发Golang的测试用例,并且提供自动化的测试支持。
同时GoConvey框架提供了丰富的Assertions支持,其中ShouldPanic系列的Assertions提供了优雅的在测试用例中测试Golang panic的方法。
GoConvey官方提供的Example是这样子的。
func TestSpec(t *testing.T) {
// Only pass t into top-level Convey calls
Convey("Given some integer with a starting value", t, func() {
x := 1
Convey("When the integer is incremented", func() {
x++
Convey("The value should be greater by one", func() {
So(x, ShouldEqual, 2)
})
})
})
}
其中的So函数中的ShouldEqual就是GoConvey提供的Assertions。
So(x, ShouldEqual, 2)
GoConvey的官方Wiki提供了所有内建的Assertions用法,用户也可按照自己的项目需要扩展自己的Assertion库。
在Golang代码中通常会使用panic抛出一些异常,来防止代码错误的发生,例如以下这个函数。
func (a *SomeClass) Union(b *SomeClass) *SomeClass {
if b == nil {
panic ("B item is nil")
}
// Some equal code
}
为了防止某些外部的代码错误,传入一个nil值的b指针,直接使用了panic。而我们在编写测试用例时,希望可以覆盖这部分的代码。
官方的文档是这样的。
// See https://github.com/smartystreets/goconvey/issues/108
So(func(), ShouldPanic)
So(func(), ShouldNotPanic)
So(func(), ShouldPanicWith, "") // or errors.New("something")
So(func(), ShouldNotPanicWith, "") // or errors.New("something")
然后我想当然的写下了下面的代码
So(a.Union(nil), ShouldPanic)
这并不会产生编译错误,因为So函数接收的是interface{}。
但是执行自动测试时,却产生了panic,不能被正确的Assertion截获,让我不解。我上查资料也不是很多。可恨的是GitHub的issues不知为何也改版了,没查到资料。
只好去看ShouldPanic Assertions的源代码。代码是这样子的:
// ShouldPanic receives a void, niladic function and expects to recover a panic.
func ShouldPanic(actual interface{}, expected ...interface{}) (message string) {
if fail := need(0, expected); fail != success {
return fail
}
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
defer func() {
recovered := recover()
if recovered == nil {
message = shouldHavePanicked
} else {
message = success
}
}()
action()
return
}
其中比较重点的几句是:
action, _ := actual.(func())
if action == nil {
message = shouldUseVoidNiladicFunction
return
}
// ...
action()
仔细看文档,才发现ShouldPanic传入的参数应该是func(),一个无参数无返回值的函数。Assertion内部会构造好defer recover接收panic,然后执行这个函数。
本来普通的Assertion如果错误的传入参数,会导致返回shouldUseVoidNiladicFunction错误。但是由于a.Union(nil)
产生了一个panic,程序不会运行到这里,所以我没有发现这个错误。
由于ShouldPanic只接受简单函数,因此简单的方法就是用一个闭包对要测试的内容进行包装。对于我的例子,正确的做法应该是。
So(func() { a.Union(nil) }, ShouldPanic)
其实在ShouldPanic自己的测试样例中也正确说明了调用方法。
func TestShouldPanic(t *testing.T) {
fail(t, so(func() {}, ShouldPanic, 1), "This assertion requires exactly 0 comparison values (you provided 1).")
fail(t, so(func() {}, ShouldPanic, 1, 2, 3), "This assertion requires exactly 0 comparison values (you provided 3).")
fail(t, so(1, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func(i int) {}, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func() int { panic("hi") }, ShouldPanic), shouldUseVoidNiladicFunction)
fail(t, so(func() {}, ShouldPanic), shouldHavePanicked)
pass(t, so(func() { panic("hi") }, ShouldPanic))
}
这样,你就可以通过GoConvey优雅的自动化测试各种可以预知的panic情况了。