go语言之路

关于 Golang 单元测试的一点实践

关于 Golang 单元测试的一点实践

为什么需要单元测试?

在没去公司之前,我一般没怎么写过单元测试,debug 代码时,直接 fmt.Println 打印即可解决,再复杂一点的话,使用 dlv 去 debug 代码,找出问题。

但是在工作中无法避免遇到一些问题:

  • 没办法在电脑上完整运行整个项目,无法看到效果
  • 写完代码,没办法部署,不知道自己写的逻辑对不对
  • 自己写的代码中会用到一些其他人写的接口,直接调用不了
  • 代码覆盖率太低,导致代码提交不了等问题

那这个时候,单元测试就非常重要了。

单元测试简单实践

下面,我们就模拟一个场景来讲解单元测试的使用。

一般的,我们称能完整运行代码,即有 main 函数的入口,但我们工作的话,一般都是写一些类似 package 的包,那么,就导致无法运行 main 函数。

解决方案即写单元测试,假设我们现在写一个叫做 add 的 package.

首先我们使用 go mod init mytest 初始化目录,使用 gomodule 管理依赖。

然后创建一个 add 的目录,mkdir add.

进入 add 目录,cd add.

创建一个文件,touch add.go

创建一个单元测试文件,touch add_test.go

此时,目录结构为:

.
├── add
│   ├── add.go
│   └── add_test.go
└── go.mod

1 directory, 3 files

我们的工作就是实现 add 包,能够实现两个数相加并返回结果的一个接口。

add.go 中写入如下内容:

package add

func Add(a, b int) int {
    return a + b + 1
}

那么我们如何测试该段代码逻辑是不是正确的呢?

add_test.go 中写入如下内容:

这里我用到了 github/stretchr/testify/assert

直接使用 go get -u github.com/stretchr/testify/assert 安装即可。

package add

import (
    "testing"

    "github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
  // 实际通过该接口得到的值
    actual := Add(1, 1)
  // 我们期望得到的值
    expected := 2
  // 是否符合我们的预期
    assert.Equal(t, expected, actual)
}

代码中,actual 为通过Add接口得到的值,expected 为我们期望得到的值,assert.Equal 即判断是否符合期望,如果不符合,会有相关信息提示。

写完上述内容之后,我们就可以进行单测了,可以使用 ide 的一键运行,也可以使用命令行进行单测,这里我们使用命令行。

go test -v .

很明显,我们期望的值是 2,而不是3,所以报错了。经过排查确实代码逻辑存在问题:

return a + b + 1

将这行代码改成即可:

return a + b

可以看到测试通过。

工作中的单元测试实践

当然单测不可能像上面写的那么的简单~

因为中间还有很多的复杂逻辑,以及调用的一系列接口,这个时候就需要借助其他的工具了,多说无益,看代码。

add.go替换为如下的内容:

package add

import (
    "encoding/json"
    "fmt"
)

type AddTwo struct {
    A int `json: "a"`
    B int `json: "b"`
}

func Add(args []byte) int {
    at, err := parseArgs(args)
    if err != nil {
        fmt.Println(err)
        return -1
    }
    return at.A + at.B + 1
}

func parseArgs(args []byte) (*AddTwo, error) {
    // http 请求或者请求了其他包中的函数等
    var at AddTwo
    err := json.Unmarshal(args, &at)
    if err != nil {
        return nil, err
    }
    return &at, nil
}

现在,Add 接口中的值,我们需要通过函数 parseArgs 去解析,但是由于某种因素我们无法直接去调用该接口。

因此我们需要通过某种手段保证我们调用的这个接口能返回我们想要的值,我们并不在乎别人写的接口是否正确,只要保证我们自己的逻辑是对的就行。

这就需要介绍一个新的东西 mock,我是这样理解的,我不关心别人接口逻辑是否正确,那是别人的工作,与我无关。我只要按照文档照着别人返回的值模拟一个值出来即可。

首先,go get -u github.com/agiledragon/gomonkey

add_test.go 替换为如下的内容:

package add

import (
    "testing"

    "github.com/agiledragon/gomonkey"
    "github.com/stretchr/testify/assert"
)

func TestAdd(t *testing.T) {
    mocks := func(t *testing.T) *gomonkey.Patches {
        patches := gomonkey.NewPatches()
        patches.ApplyFunc(parseArgs, func([]byte) (*AddTwo, error) {
            t.Log("mock parseArgs")
            // return 我们需要的值
            return &AddTwo{A: 1, B: 2}, nil
        })
        return patches
    }
    t.Run("test function add", func(t *testing.T) {
        patches := mocks(t)
        defer patches.Reset()
        args := `{"a":1, "b":2}`
        actual := Add([]byte(args))
        expected := 3
        assert.Equal(t, expected, actual)
    })
}

上述代码中,按照原本的逻辑,传入参数 args 后需要经过 parseArgs得到返回值,再计算两数之和,但是因为某种因素我们无法正常调用该函数。

通过 patcher.ApplyFuncparseArgs 的返回值设置成了我们想要的数据,避免其通过调用函数 parseArgs 返回值,保证了这段测试代码中只测试了我们自己的代码逻辑并能正确运行这段代码。

我们传进来的值为 {"a":1, "b":2},经过我们自己的 mock,得到的返回值为:

&AddTwo{
  A: 1,
  B, 2,
}

再经过我们自己的逻辑,得到返回值。从测试代码来看,正常得到的值应该为3,但是单测之后发现:

所以通过这段单测代码可以判断代码逻辑是由问题的,果然经过排查后发现:

return at.A + at.B + 1

这里应该是:

return at.A + at.B

以上就是单测的一些实践,当然真实情况还比上面写到的复杂一点,具体还需要自己去实践的哈。

参考链接

golang + gin 异步执行操作

上一篇

提高效率的工具:zsh 插件之 wd

下一篇

你也可能喜欢

发表评论

您的电子邮件地址不会被公开。 必填项已用 * 标注

提示:点击验证后方可评论!

插入图片

个人微信公众号

we-tuiguang

qq交流群

群号:1046260719

微信扫一扫

微信扫一扫