37

golang系列教程(六)—— 单元测试与mock

 4 years ago
source link: https://www.tuicool.com/articles/rMzaAj7
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.

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

  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)
    }
}
复制代码

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK