进行 Go 语言的单元测试通常使用标准库 testing 包,以 Test 开头,参数列表必须为 t *testing.T
,做压测的话使用的是 b *testing.B
。
func TestHello(t *testing.T) {
fmt.Println("Hello World")
}
在 Goland 中,直接点击绿色按钮直接就可以运行了。但是假如我们需要每次单元测试运行前调用一些公共的代码,那么就不行了,这样就必须在单元测试函数中显式地调用某些函数,这必然会导致大量冗余代码的存在。
Go 语言的测试框架提供了一种类似于 JUnit 4 的 @Before
、@After
注解的机制,也就是前置调用和后置调用。
在单元测试文件中,编写一个名称为 TestMain 的函数,而且名称必须得是这个!参数为 m *testing.M
,代码如下:
package mypackage
import (
"fmt"
"os"
"testing"
)
// 前置调用的函数
func setup() {
fmt.Println("设置前置调用")
// 执行你的准备工作
}
// 测试函数
func TestMain(m *testing.M) {
// 设置前置调用
setup()
// 运行测试
code := m.Run()
// 执行清理工作
teardown()
// 退出测试
os.Exit(code)
}
// 清理函数
func teardown() {
fmt.Println("执行清理工作")
// 执行你的清理工作
}
// 具体测试
func TestSomething(t *testing.T) {
// 执行你的测试逻辑
fmt.Println("执行测试逻辑")
}
每个测试文件中的 TestMain 函数都是彼此独立的互不干扰。接下来看一个具体的例子,以连接 Redis Server 并存入数据为例:
package main
import (
"context"
"fmt"
"os"
"testing"
"time"
"github.com/go-redis/redis/v8"
)
var client *redis.Client
var ctx = context.Background()
// 前置调用的函数
func before() {
opt, err := redis.ParseURL("redis://default:123456@127.0.0.1:6379/0?dial_timeout=1")
if err != nil {
panic(err)
}
client = redis.NewClient(opt)
fmt.Println(client)
// 执行操作时,需要获取连接
status := client.Ping(ctx)
fmt.Println(status.Result())
}
// 测试函数
func TestMain(m *testing.M) {
before()
code := m.Run()
after()
os.Exit(code)
}
func after() {
_ = client.Close()
}
func TestSetString(t *testing.T) {
status := client.Set(ctx, "name", "luobdia", time.Second*10)
fmt.Println(status.Result())
}
go-redis 包的客户端对象被放到了全局的位置,以避免每次单元测试手动创建。这样看起来是不是有面向切面编程的味道了?但是还做不到完全的像 AOP 那样强大,因为没法拿到切入点,进而没法控制调用的细节。