一、gomock概述
gomock是Go语言开发的mock框架,用来模拟接口和函数的调用。模拟是测试的关键,利用gomock可以快速创建一个虚拟实例来充当一个接口或对象的预期实例,并进行完全控制。 通常,在执行测试时,我们需要创建不同的对象或传递处理流程中的值,而在这些对象或值的基础上构建预期的行为,这就是mocking的目标。gomock帮助我们通过声明的方式来创建方法和函数的mock版本,使得我们充分控制mock的实现,从而让我们的测试更加可控。 在模拟测试中,gomock有许多非常好用的功能,例如,将需要模拟的对象注入到被测试对象中,从而使被测试对象执行预期行为。同时,gomock支持链式调用,可以根据需求进行代码优化。
二、gomock gomonkey
gomock gomonkey是gomock功能的补充,是一个用于动态修改函数的库。gomock gomonkey使用了codegen技术,通过解析代码文件生成原始函数的交换代码,而不是运行时的重载代码。因此,它比直接使用reflect和unsafe包来修改代码更加安全和可控。 gomock gomonkey提供了几个函数来替换需要测试的函数,例如:gomock.ApplyFunc、gomock.ApplyMethod、gomock.ApplyGlobalVar等等。代码示例如下:
// 原始函数
func Divide(a, b int) (int, error) {
if b == 0 {
return 0, errors.New("division by zero")
}
return a / b, nil
}
// 测试函数
func TestDivide(t *testing.T) {
patch := gomonkey.ApplyFunc(Divide, func(a, b int) (int, error) {
return 0, errors.New("mock error")
})
defer patch.Reset()
_, err := Divide(1, 0)
assert.NotNil(t, err)
}
通过ApplyFunc,我们可以针对Divide函数进行mock,让其返回一个错误信息,快速进行异常测试。
三、gomock sql queryerror
在使用gomock进行测试时,我们需要模拟数据库操作,而模拟SQL操作是测试中的一项重要的工作,gomock提供了针对SQL的模拟功能。 gomock sql queryerror是一个提供了模拟SQL查询错误的框架。在执行SQL查询操作时,数据库可能会返回各种不同类型的错误,例如:未知的列、空引用、无法连接、死锁,等等。这时,我们需要创建一个模拟类来进行测试。gomock sql queryerror提供了一个sql.DB接口,支持sql.Open、Prepare和Query三个基本函数,如下:
// 原始函数
func QueryByID(db *sql.DB, id int) (*User, error) {
row := db.QueryRow("SELECT * FROM users WHERE id = ?", id)
user := User{}
err := row.Scan(&user.ID, &user.Name, &user.Age)
if err != nil {
return nil, err
}
return &user, nil
}
// 测试函数
func TestQueryByID(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("error creating mock database: %s", err)
}
defer db.Close()
rows := sqlmock.NewRows([]string{"id", "name", "age"}).
AddRow(1, "testuser", 20)
mock.ExpectPrepare("SELECT * FROM users WHERE id = ?")
mock.ExpectQuery("SELECT * FROM users WHERE id = ?").WithArgs(1).WillReturnRows(rows)
user, err := QueryByID(db, 1)
assert.Nil(t, err)
assert.NotNil(t, user)
}
这里,我们使用了gomock sql queryerror来构建一个模型,用来模拟SQL查询。在进行测试时,我们可以通过这个模型来执行预期的SQL查询,并且可以模拟各种类型的错误,从而保证应用程序的健壮性。
四、gomock doandreturn
gomock doandreturn是gomock的另一个重要的特性,可以用来打桩。在编写测试时,我们经常需要模拟不同的返回值或异常,但有时候难以模拟或判断所有情况,gomock doandreturn为我们提供了一种灵活的方式来处理这些问题。 我们可以使用gomock doandreturn来替换需要mock的函数,这样做的好处是可以在函数执行之前、之后触发自定义的条件,例如:返回值、异常、计数等等。代码示例如下:
// 原始函数
type MyService struct{}
func (s *MyService) Action() (int, error) {
fmt.Println("step 1")
ret, err := s.Step2()
fmt.Println("step 3")
if err != nil {
return 0, err
}
return ret, nil
}
func (s *MyService) Step2() (int, error) {
fmt.Println("step 2")
return 100, nil
}
// 测试函数
func TestAction(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mock := NewMockMyService(ctrl)
mock.EXPECT().Step2().Return(200, nil)
mock.EXPECT().Step2().DoAndReturn(func() (int, error) {
fmt.Println("fake step 2")
return 200, nil
})
_, _ = mock.Action()
}
这里,我们使用gomock doandreturn来测试MyService函数的Action方法。通过替换Step2函数,我们可以测试Step2函数的返回值,并添加一个返回值为200的方法,从而模拟一个假的返回值。这样,我们就可以针对具体情况进行测试,并可以实现更加精准的结果。
五、gomock打桩
gomock打桩是将gomock用于单元测试的核心特性之一。打桩可以让我们更好地模拟系统的各种类型和状态,在实际的单元测试中使用gomock打桩可以极大地简化测试的工作。 gomock打桩的方法主要有3种,分别是EXPECT、ANY和AnyTimes:
- EXPECT:使用该方法可以规定方法被调用的精确次数。方法调用次数不与预期次数相符时,会触发panic。
- ANY:无论调用次数,该方法都会按预期返回结果,可以用于模拟返回值。
- AnyTimes:该方法只规定方法被调用的最少次数,且不会对方法调用次数做出强制要求,可以用于模拟不同次数的调用。 代码示例如下:
// 原始函数
type MyService struct{}
func (s *MyService) DoSmth(r *http.Request, writer http.ResponseWriter) {
id := r.URL.Query().Get("id")
if id == "" {
return
}
writer.WriteHeader(http.StatusOK)
writer.Write([]byte("success"))
}
// 测试函数
func TestMyService(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
req, _ := http.NewRequest("GET", "/test?id=1", nil)
recorder := httptest.NewRecorder()
mock := NewMockMyService(ctrl)
mock.EXPECT().DoSmth(req, recorder).Times(2)
mock.DoSmth(req, recorder)
mock.DoSmth(req, recorder)
}
通过上述代码,我们创建了一个DoSmth函数用于测试,利用gomock打桩的EXPECT方法规定函数被调用的精确次数为2。在测试时,我们进行了2次调用,从而通过单元测试来判断是否符合预期。
总结
gomock是一个非常强大的mock框架,通过简洁的代码和方便的API,可以让我们轻松地实现单元测试。在本文中,我们介绍了gomock的几个重要的特性,包括gospy、gomock gomonkey、gomock sql queryerror、gomock doandreturn和打桩。通过这些特性,我们可以更加自由地控制mock,并制定更精确的单元测试策略。