作者:毕晓晴
质量就是“生命”
测试的类别
回归测试:一般是手动通过终端回归一些固定的主流程场景
集成测试:是对系统功能维度做测试验证
单元测试:测试开发阶段,开发者对单独的函数、模块做功能验证,层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定这代码的质量。
单元测试规则:
所有测试文件以_test.go 结尾\
单元测试函数的命名规范,以Test开头,且Test后面的第一个字母大写,如TestDemo func TestXxx(*testing.T)
初始化逻辑放到 TestMain (需要注意的是,测试函数是并行执行的,所以不能依赖测试函数之间的执行顺序。)
单元测试的例子
安装gotests
go get -u github.com/cweill/gotests
testing 是 Golang 内置的单元测试工具包,导入的时候非常方便,不需要引用 github 仓库地址的包。
注意:单元测试方法可以自己手动写,也可以右键 VSCode 自动生成。
VS-Code中的安装插件
后便可以使用快捷键生成单元测试用例
示例:(以下是一个正确的切片代码)
package main
import "fmt"
func main() {
names := [4]string{
"John",
"Paul",
"George",
"Ringo",
}
fmt.Println(names)
a := names[0:2]
b := names[1:3]
fmt.Println(a, b)
b[0] = "XXX"
fmt.Println(a, b)
fmt.Println(names)
}
在_test.go文件中生成以下测试代码:
package main
import "testing"
func Test_main(t *testing.T) {
tests := []struct {
name string
}{
// TODO: Add test cases.
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
main()
})
}
}
testify/assert
testify是一个社区非常流行的Go单元测试工具包,其中使用最多的功能就是它提供的断言工具——testify/assert或testify/require。
asset 包的获取
使用以下命令就可以获取 asset 程序包
使用完此命令后可以在本地的 GOPATH/src 路径下发现多了三个文件夹:davecgh、stretchr、pmezard ,即为获取成功
安装
go get github.com/stretchr/testify
实例:新旧测试文件对比
以一个简单的整数相加的函数为例,分别写两个测试文件
普通测试文件:
func TestWithoutAssert(t *testing.T) {
if add(1, 3) != 4 {
t.Error("add(1+3) ne 4")
}
// this will raise an error
if add(1, 3) != 4 {
t.Error("add(1+3) ne 5")
}
if magicFunction(5) != 40 {
t.Error("magicFunction(5) ne 40")
}
}
引入assert库后的测试文件:
func TestWithAssert(t *testing.T) {
assert.Equal(t, add(1, 2), 3, "Add Error!")
//this will raise an error
assert.Equal(t, add(1, 3), 4, "Add Error!")
}
解读报错信息:(expected(应输入):4 actual(实际输入):5)按要求修改即可
调用assert程序包在测试工程量很大的项目时,测试文件会显得比较简洁,方便检查
覆盖率
go tool cover命令可以统计代码覆盖率
一般覆盖率:50%~60%,较高覆盖率80%+。
测试分支相互独立、全面覆盖
测试单元粒度足够小,函数单一职责
go tool -cover
输出类似于:
这表示我们的测试代码只覆盖了被测试代码中的10%语句。
Mock测试
-
什么是Mock对象?
- 在Mock测试中,我们使用Mock对象来替代系统中的真实对象。Mock对象具有与真实对象相同的接口和行为,但其实现是模拟的。通过使用Mock对象,我们可以在不依赖真实对象的情况下进行测试。
-
Mock测试的目的:
- 隔离测试:Mock对象使得每个测试都处于隔离的环境中,不受其他组件的影响。
- 模拟行为:通过定义Mock对象的预期行为,我们可以模拟特定情景下的返回值、异常抛出等操作。
为了进行Mock测试,我们可以使用一些第三方的库,例如 github.com/stretchr/testify/mock。这个库提供了一个简单而强大的Mock框架,用于创建和管理Mock对象。
下面是一个示例代码,展示如何使用 github.com/stretchr/testify/mock 库进行Mock测试:
首先,确保已经安装了 github.com/stretchr/testify/mock包:
go get github.com/stretchr/testify/mock
实例:使用模拟(mock)测试的概念来测试 UserService 接口的实现
首先,这段代码是一个示例的单元测试,用于测试 UserService 接口的实现。让我们一起逐行解释代码的各个部分。
package main
这是代码的包声明,表示这个文件属于 main 包。
import (
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
)
这部分是导入所需的包,其中包括 testing 包(用于编写测试)以及 github.com/stretchr/testify/assert 和 github.com/stretchr/testify/mock 这两个包,这些包提供了方便的断言和模拟工具来简化测试过程。
type UserService interface {
GetUserByID(userID int) (string, error)
}
这里定义了一个接口 UserService,它包含了一个 GetUserByID 方法,该方法接收一个 userID 参数,返回一个字符串和一个错误。
type UserServiceMock struct {
mock.Mock
}
这里定义了一个 UserServiceMock 结构体,用于模拟 UserService 接口的实现。它内嵌了 mock.Mock 类型,这是 testify/mock 包中提供的一个模拟器的基础实现。
func (m *UserServiceMock) GetUserByID(userID int) (string, error) {
args := m.Called(userID)
return args.String(0), args.Error(1)
}
这是 UserServiceMock 结构体的 GetUserByID 方法的实现。在这个方法中,我们使用了 Called 函数来记录方法调用,并返回预期的结果,以便在测试中进行断言。
func TestGetUserByID(t *testing.T) {
mockUser := "John Doe"
mockError := error(nil)
mockService := new(UserServiceMock)
mockService.On("GetUserByID", 1).Return(mockUser, mockError)
result, err := mockService.GetUserByID(1)
mockService.AssertExpectations(t)
assert.Equal(t, mockUser, result)
assert.Nil(t, err)
}
具体来说,这段代码中引入了名为 github.com/stretchr/testify/mock 的包,它提供了用于创建模拟对象的工具。在代码中,我们定义了一个 UserServiceMock 结构体,它实现了 UserService 接口,并使用 mock.Mock 嵌入字段来提供模拟功能。
接着,在 TestGetUserByID 函数中,我们创建了一个 UserServiceMock 实例 mockService,并使用 On 函数指定了 GetUserByID 方法的预期调用和返回值。这样,我们就可以通过模拟对象的方式来控制 GetUserByID 方法的行为,而不是依赖真实的实现。
最后,在调用 mockService.GetUserByID(1) 后,我们使用 mockService.AssertExpectations(t) 来验证模拟对象的行为是否满足预期。这将确保 GetUserByID 方法是否按照预期被调用。
通过使用模拟测试,我们可以隔离被测试的代码,并专注于测试特定的行为和结果,而不会受到其他依赖项的影响。这有助于提高测试的可靠性和效率,并在测试过程中更容易发现和调试问题。
基准测试
当我们进行基准测试时,我们希望对某个函数或代码段的性能进行评估和比较。在Go语言中,我们可以使用testing包中的Benchmark函数来编写基准测试。
以下是一个简单的示例,展示了如何编写一个包含基准测试的Go源代码文件:
package main
import (
"strings"
"testing"
)
// 使用“+”运算符进行字符串拼接
func concatWithPlusOperator(firstName, lastName string) string {
return firstName + " " + lastName
}
// 使用strings.Join进行字符串拼接
func concatWithJoin(firstName, lastName string) string {
return strings.Join([]string{firstName, lastName}, " ")
}
// 基准测试函数
func BenchmarkConcatWithPlusOperator(b *testing.B) {
firstName := "John"
lastName := "Doe"
for i := 0; i < b.N; i++ {
concatWithPlusOperator(firstName, lastName)
}
}
func BenchmarkConcatWithJoin(b *testing.B) {
firstName := "John"
lastName := "Doe"
for i := 0; i < b.N; i++ {
concatWithJoin(firstName, lastName)
}
}
在上述示例中,我们定义了两个函数 concatWithPlusOperator 和 concatWithJoin 来实现字符串拼接。然后,我们使用 testing.B 类型的参数,在 Benchmark 函数中循环执行这些函数。
你可以将上述代码保存为一个.go文件,比如 benchmark.go。然后,你可以在终端中运行以下命令来执行基准测试:
go test -bench=.
这将运行文件中的所有基准测试并输出结果。
请注意,基准测试需要一定的时间来执行,因此可能会花费一些时间。运行结果将显示每个基准测试的名称、运行次数和每次操作所花费的平均时间。
参考文献
掘金课程Go-语言工程实践之测试
Go 中 assert 使用