为什么需要单元测试?

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

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

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

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

单元测试简单实践

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

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

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

go mod init mytest
mkdir 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)
}

actualAddexpectedassert.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
}

AddparseArgs

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

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)
 })
}

argsparseArgs
patcher.ApplyFuncparseArgsparseArgs
{"a":1, "b":2}
&AddTwo{
  A: 1,
  B, 2,
}

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

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

return at.A + at.B + 1

这里应该是:

return at.A + at.B

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

参考链接

  • https://github.com/stretchr/testify/assert
  • https://github.com/agiledragon/gomonkey