Skip to content

Instantly share code, notes, and snippets.

@tomcatzh
Created March 17, 2015 02:47
Show Gist options
  • Save tomcatzh/8f3cd4d652c29db54cdc to your computer and use it in GitHub Desktop.
Save tomcatzh/8f3cd4d652c29db54cdc to your computer and use it in GitHub Desktop.
GoConvey ShouldPanic系列Assertions的用法

GoConvey ShouldPanic系列Assertions的用法

GoConvey简介

GoConvey是一款优秀的Golang测试框架,可以快速优雅的开发Golang的测试用例,并且提供自动化的测试支持。

同时GoConvey框架提供了丰富的Assertions支持,其中ShouldPanic系列的Assertions提供了优雅的在测试用例中测试Golang panic的方法。

GoConvey初步用法

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库。

ShouldPanic Assertions

在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情况了。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment