GO语言的单元测试 | 豆包MarsCode AI刷题

81 阅读3分钟

1. 导入使用到的库

import (
	"testing"
)

2. 单元测试的规则

  • 测试文件需要以_test.go结尾
  • 函数以Test开头,传入参数*testing.T:func TestXxx(*testing.T)
  • 初始化逻辑需要放到TestMain中:
func TestMain(m *testing.M){
    //测试前预处理
    code:=m.Run()//运行文件所有的单元测试函数
    //测试后处理
}

3. 运行单元测试

go test

指令会执行当前目录下所有的以_test.go为结尾的文件的单元测试函数 也可以运行下面的指令进行覆盖测试 运行完可以生成一个简单的测试报告:

go test -cover

QQ_1732364201839.png

也可以加一个-coverprofile=cover参数让其生成测试报告,然后使用go tool cover -html=cover -o cover.html将其转换成html形式:

image.png

4. 断言测试

go原生的测试库没有断言功能,需要引入第三方库

  1. 引入第三方库
go get github.com/stretchr/testify
  1. 使用
package yours
import (
  "testing"
  "github.com/stretchr/testify/assert"
)
func Add(a, b int) int {
    return a + b
}
func TestAdd(t *testing.T) {
    result := Add(2, 3)
    assert.Equal(t, 5, result) 
}

需要注意,在使用断言的时候,我们需要将testing.T对象作为参数传递给断言函数,函数通过它输出错误报告。

也可以换个写法,创建一个断言对象,这样后续就不用传递testing.T对象了。

断言函数返回一个bool值,指示断言是否成功。可以通过判断这个bool值来手动停止接下来的测试,不过也可以直接使用testify提供的require包,可以直接在失败的时候停止测试。

  assert := assert.New(t)
  // assert equality
  assert.Equal(123, 123, "they should be equal")

5. 更多的testify的功能

Mock

单元测试要求幂等与稳定。稳定性要求,任何时间都能运行单测 直接使用DB/redis 可能导致对网络等条件的依赖 不能随时随地的运行, 所以我们使用mock机制给函数假数据。

testify支持对一个对象打桩。

  1. 定义结构体,实现原对象的接口
type MyInterface interface {
    DoSomething() string
}

type MyMockedObject struct {
    mock.Mock
}


func (m *MyMockedObject) DoSomething(number int) (bool, error) {

  args := m.Called(number)
  //do something
  //返回预设参数
  return args.Bool(0), args.Error(1)

}

MyMockedObject 是一个模拟对象,它实现了 MyInterface 接口,并在 DoSomething 方法中使用 m.Called() 来记录调用信息。并返回一开始设定的内容。

  1. 在单测函数中使用它
func TestSomething(t *testing.T) {
  testObj := new(MyMockedObject)
  //设置muck函数名 以及我们想让他返回与接收参数的预期。
  testObj.On("DoSomething", 123).Return(true, nil)
  // 在下面这个函数中调用我们的mock对象的函数
  targetFuncThatDoesSomethingWithObj(testObj)
  // 断言输入输出都符合我们的预期
  testObj.AssertExpectations(t)
}

将输入参数设置为通配符

mockCall:=testObj.On("DoSomething", mock.Anything).Return(true, nil)

这样不管输入值是什么 都算是符合预期的。

On函数返回mockCall对象,使用mockCall.Unset()可以卸载掉一开始设置的预期,重新设置。

使用mockery进行自动的Mock代码生成

  1. 安装mockery
go install github.com/vektra/mockery/v2@v2.40.3
  1. 编辑配置文件.mockery.yaml
with-expecter: true
packages:
    github.com/your-org/your-go-project:
        # place your package-specific config here
        config:
        interfaces:
            # select the interfaces you want mocked
            Foo:
                # Modify package-level config for this specific interface (if applicable)
                config:

指定需要生成mock的包与接口,也可以指定第三方的包与接口。

例子:

编写yaml配置

quiet: False
keeptree: True
disable-version-string: True
with-expecter: True
mockname: "{{.InterfaceName}}"
filename: "{{.MockName}}.go"
dir: "mocks"
outpkg: mocks
packages:
  github.com/***/GoMall/rpc_gen/kitex_gen/product/productservice:
    interfaces:
      Client:

然后运行mockery

mockery

然后编写单元测试


func getProductRespFromProductClient(productClient productservice.Client) string {
	productResp, _ := productClient.GetProduct(nil, &product.GetProductReq{Id: 1})
	return productResp.Product.Name
}

func Test_getProductRespFromProductClient(t *testing.T) {
	mockDB := mocks.NewClient(t)
	mockDB.EXPECT().GetProduct(mock.Anything, mock.Anything).Return(&product.GetProductResp{Product: &product.Product{Name: "chocolate"}}, nil).Once()
	product_name := getProductRespFromProductClient(mockDB)
	assert.Equal(t, "chocolate", product_name)
}

最后运行测试go test

QQ_1732371318928.png

ps .mockery2.40之前的版本通过yaml文件生成的功能是开发版本有可能报错。