Go语言工程实践之测试| 青训营

90 阅读7分钟

作者:毕晓晴

质量就是“生命”

测试的类别

回归测试:一般是手动通过终端回归一些固定的主流程场景
集成测试:是对系统功能维度做测试验证
单元测试:测试开发阶段,开发者对单独的函数、模块做功能验证,层级从上至下,测试成本逐渐减低,而测试覆盖率确逐步上升,所以单元测试的覆盖率一定程度上决定这代码的质量。

image.png

单元测试规则:

所有测试文件以_test.go 结尾\

image.png

单元测试函数的命名规范,以Test开头,且Test后面的第一个字母大写,如TestDemo func TestXxx(*testing.T)

初始化逻辑放到 TestMain (需要注意的是,测试函数是并行执行的,所以不能依赖测试函数之间的执行顺序。)

单元测试的例子

安装gotests

go get -u github.com/cweill/gotests

testing 是 Golang 内置的单元测试工具包,导入的时候非常方便,不需要引用 github 仓库地址的包。

注意:单元测试方法可以自己手动写,也可以右键 VSCode 自动生成。

VS-Code中的安装插件

后便可以使用快捷键生成单元测试用例 image.png

示例:(以下是一个正确的切片代码)

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

image.png 在_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()
		})
	}
}

image.png

testify/assert

testify是一个社区非常流行的Go单元测试工具包,其中使用最多的功能就是它提供的断言工具——testify/asserttestify/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)按要求修改即可

e9540897648635293a4eea1209ef1ee.png 调用assert程序包在测试工程量很大的项目时,测试文件会显得比较简洁,方便检查

覆盖率

go tool cover命令可以统计代码覆盖率

一般覆盖率:50%~60%,较高覆盖率80%+。
测试分支相互独立、全面覆盖
测试单元粒度足够小,函数单一职责

go tool -cover

输出类似于: image.png 这表示我们的测试代码只覆盖了被测试代码中的10%语句。

Mock测试

  1. 什么是Mock对象?

    • 在Mock测试中,我们使用Mock对象来替代系统中的真实对象。Mock对象具有与真实对象相同的接口和行为,但其实现是模拟的。通过使用Mock对象,我们可以在不依赖真实对象的情况下进行测试。
  2. 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/assertgithub.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)
	}
}

在上述示例中,我们定义了两个函数 concatWithPlusOperatorconcatWithJoin 来实现字符串拼接。然后,我们使用 testing.B 类型的参数,在 Benchmark 函数中循环执行这些函数。

你可以将上述代码保存为一个.go文件,比如 benchmark.go。然后,你可以在终端中运行以下命令来执行基准测试:

go test -bench=.

这将运行文件中的所有基准测试并输出结果。

请注意,基准测试需要一定的时间来执行,因此可能会花费一些时间。运行结果将显示每个基准测试的名称、运行次数和每次操作所花费的平均时间。

参考文献

掘金课程Go-语言工程实践之测试

juejin.cn/course/byte…

Go 中 assert 使用

blog.csdn.net/qq_43267773…