Go single test series 5 - driving with monkey

Posted by Doug G on Fri, 28 Jan 2022 16:53:21 +0100

This is the fourth in a series of tutorials on Go language unit testing from zero to slip. It introduces how to use monkey in unit testing.

In the previous "Go single test series 5 - mock interface test", we introduced how to use gomock and gostub tools to mock interface and pile driving in unit test.

In this article, we will introduce a more powerful piling tool - monkey, which supports piling for arbitrary functions and methods.

The sample code of Go single test from zero to slide series has been uploaded to Github, click 👉🏻 https://github.com/go-quiz/golang-unit-test-demo View the complete source code.

monkey

introduce

monkey It is a very common piling tool in Go unit testing. It rewrites the executable file through assembly language at run time and jumps the implementation of objective function or method to pile implementation. Its principle is similar to hot patch.

The monkey library is very powerful, but you should pay attention to the following when using it:

  • monkey does not support inline functions. When testing, you need to turn off the inline optimization of Go language through the command line parameter - gcflags=-l.
  • monkey is not thread safe, so don't use it in concurrent unit tests.

install

go get bou.ke/monkey

Use example

Suppose that the middle office of your company provides a user center library varys, which can easily obtain user related information according to uid. However, when you write code, the library has not been implemented, or the library needs to be requested by the intranet, but you don't have this ability now. At this time, you need to do some mock work to write unit tests for MyFunc.

// func.go

func MyFunc(uid int64)string{
	u, err := varys.GetInfoByUID(uid)
	if err != nil {
		return "welcome"
	}

	// Here are some logic codes

	return fmt.Sprintf("hello %s\n", u.Name)
}

We use the monkey library for varys Getinfobyuid for piling.

// func_test.go

func TestMyFunc(t *testing.T) {
	// Yes, varys Getinfobyuid for piling
	// & varys. Is returned regardless of the uid passed in UserInfo{Name: "liwenzhou"}, nil
	monkey.Patch(varys.GetInfoByUID, func(int64)(*varys.UserInfo, error) {
		return &varys.UserInfo{Name: "liwenzhou"}, nil
	})

	ret := MyFunc(123)
	if !strings.Contains(ret, "liwenzhou"){
		t.Fatal()
	}
}

Perform unit tests:

Note: the - gcflags=-l parameter is added here to prevent inline optimization.

go test -run=TestMyFunc -v -gcflags=-l

Output:

=== RUN   TestMyFunc
--- PASS: TestMyFunc (0.00s)
PASS
ok      monkey_demo     0.009s

In addition to mocking functions, monkey also supports mocking methods.

// method.go

type User struct {
	Name string
	Birthday string
}

// CalcAge calculates user age
func (u *User) CalcAge() int {
	t, err := time.Parse("2006-01-02", u.Birthday)
	if err != nil {
		return -1
	}
	return int(time.Now().Sub(t).Hours()/24.0)/365
}


// GetInfo get user related information
func (u *User) GetInfo()string{
	age := u.CalcAge()
	if age <= 0 {
		return fmt.Sprintf("%s It's mysterious. We don't know yet ta. ", u.Name)
	}
	return fmt.Sprintf("%s this year%d Years old, ta It's our friend.", u.Name, age)
}

If the function of the CalcAge method has not been completed when we write the unit test for GetInfo, we can use monkey at this time.

// method_test.go

func TestUser_GetInfo(t *testing.T) {
	var u = &User{
		Name:     "q1mi",
		Birthday: "1990-12-20",
	}

	// Piling for object method
	monkey.PatchInstanceMethod(reflect.TypeOf(u), "CalcAge", func(*User)int {
		return 18
	})

	ret := u.GetInfo()  // An internal call to the u.CalcAge method returns 18
	if !strings.Contains(ret, "friend"){
		t.Fatal()
	}
}

Perform unit tests:

❯ go test -run=User -v
=== RUN   TestUser_GetInfo
--- PASS: TestUser_GetInfo (0.00s)
PASS
ok      monkey_demo     0.012s

monkey can basically meet any needs we need in unit testing.

There is also a reference monkey Library in the community gomonkey The principle and use process of the library are basically similar, so I won't be verbose here. In addition, there are other piling tools in the community, such as GoStub (the previous article introduced piling for global variables), etc.

Skilled use of various piling tools can enable us to write qualified unit tests faster and escort our software.

summary

Through two examples of external function dependency and internal method dependency, this paper introduces how to use monkey to analyze the dependent functions and methods.

In the next article, we will introduce the common tool for writing unit tests - goconvey.

Topics: Go