4

#Unit Test# Unit Test Pre-study and Practice in Golang

 1 year ago
source link: https://lottewong.github.io/2021/08/16/Unit%20Test%20Pre-study%20and%20Practice%20in%20Golang/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

If you write code, write tests. – The Way of Testivus


目录 Table of Contents


背景

单元测试的重要性无需多言,但是由于业务迭代得超快,就很难再分出时间和精力投入到质量保障中去。尽管如此,等待接口联调的过程中既漫长无比又压力山大(毕竟谁想被挂 Tapd Bug 单呢 _(:з」∠)_

在之前的 Unit Test Pre-study and Practice in Python 文章中,已经简要地介绍了测试流程和测试用例的核心理念,本文将沿用这些方法论以及结合各位大神的博客和日常工作的体验,着重讨论以下内容:

  • 技术选型Golang 作为一门静态和强类型语言,相比于 Python 的动态和弱类型特性,带来了一些新的挑战。
  • 工程实践:业务逻辑快速验证的最佳实践,暂时不涉及脚手架设计。

技术选型

testing

官方原生库,无断言机制,编写较繁琐。

  1. 编写

    func DoSomething() error {
    // ...
    }

    func TestDoSomething(t *testing.T) {
    // Arrange
    // ...

    // Act
    err := DoSomething()

    // Assert
    if err != nil {
    t.Errorf(err.Error())
    }
    }
  2. 运行

    go test -v

GoConvey

兼容官方原生库和模拟框架,有断言机制,编写较简洁。

  1. 编写

    func DoSomething() error {
    // ...
    }

    func TestDoSomething(t *testing.T) {
    Convey("TestDoSomething", t, func() {
    Convey("Condition 1", func() {
    // Arrange
    // ...

    // Act
    err := DoSomething()

    // Assert
    So(err, ShouldBeNil)
    })
    })
    }
  2. 运行

    # Cmd
    go test -v
    # Web
    $GOPATH/bin/goconvey

GoMonkey

Mock 全局变量 & 函数方法

  1. 全局变量

    patches := ApplyFuncVar(&funcVar, mockVar)
    defer patches.Reset()

    patches := ApplyGlobalVar(&funcVar, mockVar)
    defer patches.Reset()
  2. 函数方法

    patches := ApplyFunc(funcName, mockFunc)
    defer patches.Reset()

    var ptr *SomeClass
    patches := ApplyMethod(reflect.TypeOf(ptr), "methodName", mockMethod)
    defer patches.Reset()

GoMock

Mock 接口

  1. 生成

    // repository.go
    // Repository is the interface to be mocked
    type Repository interface {
    Create(val interface{}) error
    Get(key string) (interface{}, error)
    Update(key string, val interface{}) error
    Delete(key string) error
    }

    // mockgen -source=${file}
    // mockgen ${package} ${interface1}, ... , ${interfaceN}

    // mock_repository.go
    // MockRepository is a mock of Repository interface
    type MockRepository struct {
    ctrl *gomock.Controller
    recorder *MockRepositoryMockRecorder
    }

    // MockRepositoryMockRecorder is the mock recorder for MockRepository
    type MockRepositoryMockRecorder struct {
    mock *MockRepository
    }

    // NewMockRepository creates a new mock instance
    func NewMockRepository(ctrl *gomock.Controller) *MockRepository {
    mock := &MockRepository{ctrl: ctrl}
    mock.recorder = &MockRepositoryMockRecorder{mock}
    return mock
    }

    // EXPECT returns an object that allows the caller to indicate expected use
    func (_m *MockRepository) EXPECT() *MockRepositoryMockRecorder {
    return _m.recorder
    }

    // Create mocks base method
    func (_m *MockRepository) Create(_param0 []byte) error {
    // ...
    }

    // Get mocks base method
    func (_m *MockRepository) Get(_param0 string) (interface{}, error) {
    // ...
    }

    // Update mocks base method
    func (_m *MockRepository) Update(_param0 string, _param1 interface{}) error {
    // ...
    }

    // Delete mocks base method
    func (_m *MockRepository) Delete(_param0 string) error {
    // ...
    }
  2. 使用

    ctrl := gomock.NewController(t)
    defer ctrl.Finish()
    mockRepo := mock_repo.NewMockRepository(ctrl)

    InOrder (
    createCall := mockRepo.EXPECT().Create(mockVal).Return(mockErr)
    getCall := mockRepo.EXPECT().Get(mockKey).Return(mockVal, mockErr).Times(7)
    updateCall := mockRepo.EXPECT().Update(mockKey, mockVal).Return(mockErr)
    deleteCall := mockRepo.EXPECT().Create(mockKey).Return(mockErr)
    )

sqlmock

Mock 数据库

// Arrange
db, mock, err := sqlmock.New()
defer db.Close()

rows := sqlmock.NewRows(colKeys).AddRow(colVals)
mock.ExpectQuery(sqlStatement).WillReturnRows(rows)

// Act
res, err := db.Query(sqlStatement)
defer res.Close()

// Assert
for res.Next() {
res.Scan(&fileds)
// ...
}

httptest

Mock 服务器

// Arrange
r := httptest.NewRequest(method, url, nil)
w := httptest.NewRecorder()

// Act
handler(w, r)

// Assert
res := w.Result()
// ...

工程实践

  • to be continued…

总结

  • 单测框架用于断言:使用 GoConvey 框架。

  • 模拟框架用于替换:对于全局变量和函数方法使用 GoMonkey 框架,对于接口使用 GoMock 框架,对于数据库使用 sqlmock 框架,对于服务器使用 httptest 框架。

参考链接

以下文章对本文亦有贡献 :)

附录

使用文档和最佳实践 :)



About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK