深入了解gomock

发布时间:2023-05-20

一、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,并制定更精确的单元测试策略。