golang 单元测试

单元测试介绍

为了保证代码的质量,很多公司都会要求写单元测试。这里介绍两个指标,

  1. 函数覆盖率:函数调用个数/函数个数,通常要求100%
  2. 行覆盖率:走到的行的个数/总函数,通常要求>60%

通过单元测试,我们可以针对不同场景进行测试,是研发自己对质量的把控。 笔者目前所在的公司对单元测试要求很高,并且有替代测试的趋势。

go test

  • go的test一般以xxx_test.go为文件名,xxx并没有特别要求是要实测的文件名
  • TestMain作为初始化test
  • Testxxx(t* testing.T)
  • go test 即可运行单元测试
  • go test --v --test.run funcName 可以指定单测某个方法

创建一个client包进行示例,包结构如下

.
├── client.go
├── client_test.go
复制代码
func TestUser(t *testing.T) {
    var u = &User{Age: 15, Name: "alice"}
    if u.Age != 15 {
        t.Error("age err")
    }   
    if u.Name != "bob" {
        t.Error("name err")
    }   
}
复制代码

由于Name不符合预期,则会有如下提示

--- FAIL: TestUser (0.00s)
    client_test.go:28: name err
FAIL
exit status 1
FAIL	test/mocktest	0.005s
复制代码

go convey

goconvey可以很好的支持setup和teardown,goconvey可以在运行单个测试用例前都进行一次状态初始化和销毁。goconvey还有很多已经定义好了的能够直接使用的assert函数,并且可以自定义assert函数。 常用的assert如下:

var (
    ShouldEqual          = assertions.ShouldEqual
    ShouldNotEqual       = assertions.ShouldNotEqual

    ShouldBeGreaterThan          = assertions.ShouldBeGreaterThan
    ShouldBeGreaterThanOrEqualTo = assertions.ShouldBeGreaterThanOrEqualTo
    ShouldBeLessThan             = assertions.ShouldBeLessThan
    ShouldBeLessThanOrEqualTo    = assertions.ShouldBeLessThanOrEqualTo
    ShouldBeBetween              = assertions.ShouldBeBetween
    ShouldNotBeBetween           = assertions.ShouldNotBeBetween
    ShouldBeBetweenOrEqual       = assertions.ShouldBeBetweenOrEqual
    ShouldNotBeBetweenOrEqual    = assertions.ShouldNotBeBetweenOrEqual
    ShouldContainSubstring    = assertions.ShouldContainSubstring
    ShouldNotContainSubstring = assertions.ShouldNotContainSubstring

    ShouldPanic        = assertions.ShouldPanic
    ShouldBeError      = assertions.ShouldBeError
)
复制代码

使用举例:

func TestUser(t *testing.T) {
    convey.Convey("TestUser", t, func() {
        var u = &User{Age: 15, Name: "alice"}
        convey.So(u.Age, convey.ShouldEqual, 15) 
        convey.So(u.Name, convey.ShouldEqual, "bob")
    })  
}
复制代码

由于Name不符合预期,会出现如下提示

  Line 30:
  Expected: 'bob'
  Actual:   'alice'
  (Should be equal)
复制代码

mock

什么是mock

单元测试的时候,如果流程中有第三方依赖怎么办?比如当贷款支付的时候,需要用户的额度,而额度信息存在于另一个微服务,需要rpc拉取。为了解决这种场景,我们可以使用mock这种方式。简单来说,mock就是能指定依赖接口的输入 输出,可以理解为提前插入的固定数据,如此,流程就能正常跑起来。

使用mockery进行mock

限制:

  • 只能针对接口进行mock

使用流程

  • 安装mockery:go get github.com/vektra/mockery/.../
  • mockery -name=接口名,生成mocks目录。

rpc接口定义,接口实现

client.go:


package rpc 

//go:generate mockery -name=Client

type Client interface {
    Get(key string) (data interface{}, err error)
}

type ClientImpl struct {
    Ct Client
}

func (p *ClientImpl) Get(key string) (data interface{}, err error) {
    if mockCondition  {
        return p.Ct.Get(key)
    }
    // real logic
}
复制代码

client_test.go

package rpc 

import (
    "fmt"
    "test/mocktest/mocks"
    "testing"
)

type User struct {
    Age  int 
    Name string
}

func TestMock(t *testing.T) {
    convey.Convey("TestMock", t, func() {
        mc := &mocks.Client{}
        var u = &User{Age: 15, Name: "alice"}
        mc.On("Get", "alice").Return(u, nil)
        ci.Ct = mc
        data, err := ci.Get("alice")
        convey.So(data.Age, convey.ShouldEqual, 15)
    }
}
复制代码