这是我参与「第五届青训营 」伴学笔记创作活动的第 3 天
GO语言进阶-工程进阶
一、语言进阶
1、Goroutine
协程(goroutine) 是轻量级的执行线程。
2、Channel
通道(channels) 是连接多个协程的管道。 你可以从一个协程将值发送到通道,然后在另一个协程中接收。
默认情况下,通道是 无缓冲 的,这意味着只有对应的接收(<- chan) 通道准备好接收时,才允许进行发送(chan <-)。 有缓冲通道 允许在没有对应接收者的情况下,缓存一定数量的值。
3、Sync
二、依赖管理
GO Module 依赖管理方案
GO语言的模块依赖管理方案是使用go mod来管理依赖。Go mod是Go语言官方提供的依赖管理工具,它可以让开发者在开发过程中轻松管理第三方库依赖,支持自动下载、更新、管理版本等功能。使用go mod进行依赖管理,可以使项目更加稳定、可维护。
当你想使用第三方库时, 可以在代码中导入它:
import "github.com/user/library"
然后在命令行运行:
go mod init # 初始化go.mod文件
go mod tidy # 下载依赖并管理版本
如果你想更新库版本,可以运行:
go mod edit -require=github.com/user/library@latest
如果你想查看当前库版本,可以运行:
go list -m -json all
这些命令将会自动管理你的依赖项并且更新go.mod文件和go.sum文件。 go.mod文件记录了你的项目所需要的库及其版本信息,go.sum文件记录了依赖项的校验和,用于确保依赖项的安全性。
三、测试
想要写出好的 Go 程序,单元测试是很重要的一部分。 testing 包为提供了编写单元测试所需的工具,写好单元测试后,我们可以通过 go test 命令运行测试。
1、单元测试
单元测试是指对程序中的独立部分进行测试,以确保其正确性。在Go语言中,单元测试使用"testing"包进行编写。
一个简单的单元测试示例如下:
package mymath
import "testing"
func TestSum(t *testing.T) {
total := Sum(5, 5)
if total != 10 {
t.Errorf("Sum was incorrect, got: %d, want: %d.", total, 10)
}
}
在上面的代码中,我们定义了一个"TestSum"函数,用于测试"mymath"包中的"Sum"函数。我们通过调用"Sum"函数并将结果与预期值比较来确定它是否正确。如果不正确,则使用"t.Errorf"函数记录错误信息。
在命令行中运行"go test"命令来运行所有单元测试。例如,如果要运行上面示例中的测试,可以运行以下命令:
go test
运行测试时,Go会自动查找所有以"Test"开头的函数并运行它们。如果所有测试都通过,则不会有任何输出。如果有任何测试失败,则会输出错误信息。
除了上面的示例, 还可以使用 t.Run() 函数来跑子测试,使用 t.Parallel() 函数让测试可以并行运行,使用 t.Log() 函数记录调试信息等,这些都是很重要的测试
使用 "t.Run" 函数可以在单个测试函数中运行多个子测试。这样可以使测试更具可读性和可维护性。
例如,假设我们有一个 "calculator" 包,其中包含 "Add"、"Subtract"、"Multiply" 和 "Divide" 函数。我们可以使用 "t.Run" 函数在单个测试函数中测试这些函数。
package calculator
import "testing"
func TestCalculator(t *testing.T) {
t.Run("Add test", func(t *testing.T) {
result := Add(1, 2)
if result != 3 {
t.Errorf("Add was incorrect, got: %d, want: %d.", result, 3)
}
})
t.Run("Subtract test", func(t *testing.T) {
result := Subtract(3, 2)
if result != 1 {
t.Errorf("Subtract was incorrect, got: %d, want: %d.", result, 1)
}
})
t.Run("Multiply test", func(t *testing.T) {
result := Multiply(3, 2)
if result != 6 {
t.Errorf("Multiply was incorrect, got: %d, want: %d.", result, 6)
}
})
t.Run("Divide test", func(t *testing.T) {
result, err := Divide(6, 2)
if err != nil {
t.Errorf("Unexpected error: %s", err)
}
if result != 3 {
t.Errorf("Divide was incorrect, got: %d, want: %d.", result, 3)
}
})
}
2、Mock测试
Mock 测试是一种在单元测试中使用的技术,它允许测试模拟对象的行为,而无需真正的实现。它可以帮助我们在测试中隔离系统的不同部分,并且可以在不需要实际数据的情况下进行测试。这样可以更快、更简单、更可靠地测试代码之间的交互,并且可以更好地测试所有与实际实现无关的代码。
通常使用 mock 测试来替代外部依赖(如数据库,API等),因为它们不易于控制,或者它们对测试结果产生了不必要的影响。使用 mock 测试可以确保测试是独立的,并且可以在不同环境中重复运行。
package mypackage
type MyService interface {
DoSomething(input string) (string, error)
}
type MyStruct struct {
MyService MyService
}
func (m *MyStruct) MyFunc(input string) (string, error) {
result, err := m.MyService.DoSomething(input)
if err != nil {
return "", err
}
return "prefix-" + result, nil
}
上面是一个简单的结构体和函数,它们依赖于一个叫做 MyService 的接口来完成一些操作。为了测试 MyFunc 函数,我们可以使用 GoMock 库来模拟 MyService 接口。
package mypackage_test
import (
"testing"
"github.com/golang/mock/gomock"
mypackage "mypackage"
mock "mypackage/mock_mypackage"
)
func TestMyFunc(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
mockService := mock.NewMockMyService(ctrl)
mockService.EXPECT().DoSomething("input").Return("output", nil)
s := &mypackage.MyStruct{MyService: mockService}
result, err := s.MyFunc("input")
if err != nil {
t.Errorf("Unexpected error: %v", err)
}
if result != "prefix-output" {
t.Errorf("Unexpected result: %v", result)
}
}
这里,我们使用 GoMock 库来创建一个 mockService 对象,并且设置它的 DoSomething 方法在接收 "input" 参数时返回 "output" 和 nil。然后将这个 mockService 作为 MyStruct 的依赖传入,并调用 MyFunc("input") 来进行测试,结果是否符合预期。
这个例子只是简单的演示了如何使用 GoMock 库来模拟一个接口并进行测试,实际应用中,你可能需要更复杂的模拟,例如模拟多次调用、使用自定义匹配器等。这些都可以通过 GoMock 库的不同方法来实现。
另外,在实际项目中,你可能需要使用更为灵活的 mock 测试框架,例如 testify/mock 、 mockgen 等。
总之,mock 测试是一种非常有用的技术,它可以帮助你更好地测试你的代码,并且可以更好地隔离系统的不同部分。使用 Go 语言的 mock 测试框架来实现这一技术可以更方便地完成测试。
3、基准测试
基准测试(benchmark test)是一种用于测量代码性能的测试方式。它可以帮助你了解代码在不同条件下的性能表现,并且可以帮助你发现性能瓶颈。
在 Go 语言中,可以使用 testing 包中的 Benchmark 函数来实现基准测试。这个函数需要传入一个指向 testing.B 类型的指针,并且需要在函数内部使用 b.N 和 b.ResetTimer 等方法来进行测试。
下面是一个简单的例子,它测量了一个函数在不同数据规模下的性能:
package mypackage
func BenchmarkMyFunc(b *testing.B) {
for n := 0; n < b.N; n++ {
MyFunc(1000)
}
}
在上面的例子中,我们使用了 testing.B 类型的变量 b 来进行测试,并且使用了一个循环来调用 MyFunc 函数,每次传入的参数值为 1000。这样就可以测量 MyFunc 函数在不同数据规模下的性能。
在运行基准测试时,需要使用 go test 命令并加上 -bench 参数,例如:go test -bench=.
还可以加上 -benchtime 参数来指定测试的时间,以及 -benchmem 参数来查看内存分配的统计信息