[Lib hay mỗi tuần] Goconvey - Thay đổi cách test của bạn

Lần này mình sẽ review về một Go lib hay ho - GoConvey

TL;DR

Một fancy testing tool cho Gophers. Có hỗ trợ Browser UI và một vài concept theo mình là khá hay.

Một số đặc điểm

Cách cài đặt

go get github.com/smartystreets/goconvey

Cách sử dụng

Giờ mình sẽ có vài cái hàm đơn giản như sau:


func Add(a, b int) int {
	return a + b
}

func Subtract(a, b int) int {
	return a - b
}

func Multiply(a, b int) int {
	return a * b
}

func Division(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("can not divide by zero")
	}
	return a / b, nil
}

Giờ test nó bằng Covey xem sao.

Để sử dụng thì các bạn import . "github.com/smartystreets/goconvey/convey".

Dấu “.” ở đây cho phép được dùng thẳng function trong convey luôn mà không cần gọi thông qua identifier (gọi thẳng Convey thay vì convey.Convey). Code sẽ nhìn clear hơn.

Test thử hàm Add

func TestAdd(t *testing.T) {
	Convey("Add two numbers", t, func() {
		So(Add(1, 2), ShouldEqual, 3)
	})
}

Một function test sẽ bắt đầu bằng từ khóa Convey. Ở trên hàm TestAdd sẽ thử dùng hàm Add cộng 2 số 1 và 2 và test xem thử có đúng bằng 3 không. Việc assert ở đây là thông qua từ khóa So.

Các bạn có thể thấy giống như viết văn vậy: So Add(1,2) should equal 3.

Viết thử hàm khó hơn xem. Hàm chia. Hàm chia thì mình cần phải xét thêm 1 điều kiện là chia cho 0. Hàm test của chúng ta có nội dung như sau.

func TestDivision(t *testing.T) {
	Convey("Divide one by another", t, func() {

		Convey("Divide by non-zero number", func() {
			num, err := Division(10, 2)
			So(err, ShouldBeNil)
			So(num, ShouldEqual, 5)
		})

		Convey("Divide by zero", func() {
			_, err := Division(10, 0)
			So(err, ShouldNotBeNil)
		})
	})
}

Dễ hiểu quá đúng không?

Khi chạy chúng ta chạy bằng lệnh go test builtin của go. Kết quả sẽ được:

alt text

Với mỗi So pass chúng ta sẽ được 1 dấu tick. Khi bạn viết assert nhiều value thì sẽ ra nhiều dấu tick rất đẹp mắt. So fail thì sẽ ra dấu X.

Goconvey ngoài ra còn giúp chúng ta visualize testcases thông qua web portal.

Các bạn chạy command goconvey. Nó sẽ đưa các bạn đến trình duyệt ở địa chỉ http://127.0.0.1:8080/.

alt text

So cool!

Execution Order

Một điểm đặc biệt sẽ khiến hầu hết các bạn what the heck trong lần đầu tiên dùng convey là: “Quái sao cái giá trị của mình nó lúc thế này lúc thế kia vậy???”

Lấy một ví dụ nhé

func TestSomething(t *testing.T) {
	Convey("Prepare", t, func() {
		a := 1
		b := 2
		Convey("Add 1", func() {
			a = a + 1
			So(a, ShouldEqual, 2)
			b = b + 1
			So(b, ShouldEqual, 3)
		})

		Convey("Subtract 1 ", func() {
			a = a - 1
			So(a, ShouldEqual, ??)
			b = b - 1
			So(b, ShouldEqual, ??)
		})
	})
}

Theo các bạn thì ví dụ ở trên giá trị tương ứng với 2 dấu ??là gì? Nhìn thì trên xuống, a = 1, b = 2. Sau khi đi qua hàm Add 1 thì a = 2, b = 3. Sau khi đi qua Subtract 1 thì a lại về 1 và b về 2 đúng không?

Hoàn toàn không. Kết quả sẽ là a = 0 và b = 1. alt text

… What the heck? Giá trị của a unexpected. Well xin giới thiệu với các bạn, execution order của Convey:

Ví dụ có structure convey như sau

Convey A
    So 1
    Convey B
        So 2
    Convey C
        So 3

Thứ tự thực hiện không phải là A1B2C3 nhé. Mà là A1B2A1C3. :))

Convey có thứ tự execution như vậy là để hỗ trợ setup và tear down một cách tự nhiên hơn.

			Convey("Create", func() {
				Convey("Success", func() {
					accountID := uuid.NewV4().String()
					role := randomRole(accountID, randomdata.StringNumber(4, "-"))
					_, err = core.CreateRole(role)
					So(err, ShouldBeNil)

					Reset(func() {
						arango.TruncateCollectionForTesting(
							cArango.RoleCollection,
						)
					})

					Convey("AlreadyExist", func() {
						role.ID = ""
						_, err := core.CreateRole(role)
						So(err, ShouldEqual, ErrRoleAlreadyExists)
					})

					Convey("GetByID", func() {
						r, err := core.GetRoleByID(role.ID)
						So(err, ShouldBeNil)
						So(r, ShouldResemble, role)
					})

					Convey("GetByType", func() {
						r, err := core.GetRoleByType(role.Type)
						So(err, ShouldBeNil)
						So(r, ShouldResemble, role)
					})
         })
})

Ví dụ như đoạn code trên, việc setup teardown của mình chỉ cần khai báo một lần. Và các Convey phía sau sẽ có isolated context, giúp cho các test cases của mình không depend lẫn nhau.

Tổng kết

Xài Convey đi.

  • LinkedIn
  • Tumblr
  • Reddit
  • Google+
  • Pinterest
  • Pocket