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
也可以加一个-coverprofile=cover参数让其生成测试报告,然后使用go tool cover -html=cover -o cover.html将其转换成html形式:
4. 断言测试
go原生的测试库没有断言功能,需要引入第三方库
- 引入第三方库
go get github.com/stretchr/testify
- 使用
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支持对一个对象打桩。
- 定义结构体,实现原对象的接口
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() 来记录调用信息。并返回一开始设定的内容。
- 在单测函数中使用它
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代码生成
- 安装mockery
go install github.com/vektra/mockery/v2@v2.40.3
- 编辑配置文件
.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
ps .mockery2.40之前的版本通过yaml文件生成的功能是开发版本有可能报错。