[Go]Go单元测试的前置和后置调用--TestMain

2,033 阅读2分钟

image.png

进行 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 那样强大,因为没法拿到切入点,进而没法控制调用的细节。