现在,你已经基本了解了Go单元测试的函数写法,以及是如何执⾏的。⽽且你也知道⼀个单元测试的函数的函数签名如下所示:
func TestXxx(t *testing.T) {
...
}
注意,这个函数是没有返回类型的,⽽且必须有且只有⼀个testing.T
的参数。当单元测试执⾏的时候,它会传⼊ *testing.T
的参数,你可以在函数体中使⽤这个参数,标记测试是否成功或者失败,输出⼀些⽇志,或者执⾏⼦测试。
*testing.T
和⽤来做benchmark的 *testing.B
以及做模糊测试的 *testing.F
⼀样,都实现了 testing.TB
接⼝。这个接⼝的名字也很有趣,应该先前是 T
、 B
共同的接⼝,所以叫 TB
,不过后来Go 1.18加⼊了Fuzzing
测试,所以接⼝名是不是应该是 TBF
了?不过这不重要,⼀般我们很少直接使⽤ *testing.TB
。
这⼀章我们只介绍 *testing.T
,也就顺便介绍了 TB
的⽅法。 T
、 B
、 F
特有的⽅法我们遇到的时候专⻔介绍。
我将 *testing.T
的⽅法按照功能分成⼏类进⾏介绍。
标记测试是否失败 (Fail/FailNow/Failed/Fatal/Fatalf)
- Fail: 标记当前测试失败,但是后续的代码还是继续执⾏,不会中断
- FailNow: 标记当前测试失败,并且调⽤
runtime.Goexit
停⽌本goroutine的执⾏。 - 因为只是调⽤的
runtime.Goexit
,⽽不是os.Exit
,所以它只是停⽌了本测试,后续的测试还是会被执⾏。 - Fatal: 先输出⼀个⽇志(Log),再标记当前测试失败停⽌执⾏。等价于
Log
+FailNow
。 - Fatalf: 等价于
Logf
+FailNow
。 - Failed: 返回当前函数是否已经被标记为failed。
例⼦:
package s2
import (
"testing"
"time"
)
func TestFail(t *testing.T) {
t.Fail()
t.Log("after Fail")
t.FailNow()
t.Log("after FailNow")
}
func TestFatal(t *testing.T) {
t.Fatal("fataled")
t.Log("after Fatal")
}
func TestFatalf(t *testing.T) {
t.Fatalf("there is: %v", "Fatalf")
t.Log("after Fatalf")
}
func TestFailNowInAnotherGoroutine(t *testing.T) {
go func() {
t.FailNow()
t.Log("after FailNow in another goroutine")
}()
time.Sleep(time.Second)
t.Log("after one second")
}
输出⽇志 (Log/Logf/Error/Errorf)
这⼏个⽅法⽤来输出⽇志信息。
- Log: 类似
Println
,输出到error log
中。对于单元测试,只有测试失败或者使⽤-test.v
参数时,才会真正输出⽇志。但是对于benchmark,总是会输出。 - Logf: 类似
Printf
,可以输出格式化的⽇志。 - Error: 等价于
Log
+Fail
。 - Errorf: 等价于
Logf
+Fail
。
例⼦:
package s2
import (
"testing"
"time"
)
func TestLog(t *testing.T) {
t.Log("it is a log")
t.Logf("it is a log at %v", time.Now().Format(time.RFC1123))
t.Error("it is an error")
t.Errorf("it is an error at %v", time.Now().Format(time.RFC1123))
}
跳过(Skip/SkipNow/Skipped/equivalent)
- SkipNow: 跳过本测试,并调⽤
runtime.Goexit
停⽌执⾏本测试。 - Skip: 等价于
Log
+SkipNow
。 - Skipf: 等价于
Logf
+SkipNow
。 - Skipped: 返回当前测试是否被跳过。
例子:
package s2
import (
"runtime"
"testing"
)
func TestSkip(t *testing.T) {
if runtime.GOOS == "darwin" {
t.Skip("skip MacOS")
}
if testing.Short() {
t.Skip("skip because of short")
}
t.Log("there a non-skipped log")
}
那么什么时候使⽤Skip⽅法呢?
⽐如你在本机编写单元测试函数,这些函数依赖⼀些基础的服务,或者只能在Linux环境下运维⽽你的机器是Mac,这个时候你可能不想执⾏这些单元测试,⽽是在依赖可⽤或者Linux环境下才运⾏。你可以编写好这些单元测试,指定在某种环境下直接跳过,⽽不是报错。
还⽐如有些单元测试需要耗费⽐较⻓的时间。当传⼊ -short 参数时,我们就跳过这些耗时⻓的测试。
Helper
有时候,我们在编写单元测试代码时,可能会使⽤⼀些辅助的函数,这些函数我们称之为 helper
函数。当输出错误信息的时候,会显示辅助函数的信息,这些信息实际是不必要的,所以我们可以在这些函数的开始位置调⽤ Helper
⽅法,将此函数标记为辅助函数,这样⽇志中就不会显示这些辅助函数的信息了。
⽐如下⾯这个来⾃于官⽅的例⼦:
package s2
import "testing"
func notHelper(t *testing.T, msg string) {
t.Error(msg)
}
func helper(t *testing.T, msg string) {
t.Helper()
t.Error(msg)
}
func notHelperCallingHelper(t *testing.T, msg string) {
helper(t, msg)
}
func helperCallingHelper(t *testing.T, msg string) {
t.Helper()
helper(t, msg)
}
func TestHelper(t *testing.T) {
notHelper(t, "0")
helper(t, "1")
notHelperCallingHelper(t, "2")
helperCallingHelper(t, "3")
fn := func(msg string) {
t.Helper()
t.Error(msg)
}
fn("4")
t.Helper()
t.Error("5")
t.Run("sub", func(t *testing.T) {
helper(t, "6")
notHelperCallingHelper(t, "7")
t.Helper()
t.Error("8")
})
}
Parallel
当⼀个单元测试的函数标记为可以并发执⾏的时候,它可以并且只能和其它标记为Parallel的函数并发执⾏。
当使⽤ -test.count
或 -test.cpu
参数时,同⼀个单元测试的多个实例之间不会并发执⾏。
package s2
import (
"net/http"
"testing"
)
func TestParallel(t *testing.T) {
var urls = map[string]string{"baidu": "http://baidu.com", "bing":
"http://bing.com", "google": "http://google.com"}
for k, v := range urls {
v := v
ok := t.Run(k, func(t *testing.T) {
t.Parallel()
t.Logf("start to get %s", v)
resp, err := http.Get(v)
if err != nil {
t.Fatalf("failed to get %s: %v", v, err)
}
resp.Body.Close()
})
t.Logf("run: %t", ok)
}
}
Cleanup
有时候我们写单元测试的时候,需要做⼀些准备的动作,⽐如建⽴与数据库的连接,打开⼀个⽂件、创建⼀个临时⽂件等等,执⾏案单元测试我们还想执⾏⼀些清理的动作,⽐如关闭与数据库的连接,关闭⽂件,删除临时⽂件等等,这个时候我们就可以使⽤Cleanup⽅法了:
package s2
import (
"io/ioutil"
"os"
"path/filepath"
"testing"
)
func TestWrite(t *testing.T) {
tempDir, err := ioutil.TempDir(".", "temp")
if err != nil {
t.Errorf("create tempDir: %v", err)
}
t.Cleanup(func() { os.RemoveAll(tempDir) })
err = ioutil.WriteFile(filepath.Join(tempDir, "test.log"), []byte("hello test"), 0644)
if err != nil {
t.Errorf("want writing success but got %v", err)
}
}
TestMain
Go还提供了TestMain
的功能。 TestMain
并不是⽤来测试main函数,⽽是对同⼀个package下的所有测试有统⼀的控制。
⽐如所有的测试都数据数据库的连接,那么可以在 m.Run 之前把数据库连接准备好,在执⾏完测试之后把数据库关闭。
你可以看到它和 Cleanup
不太⼀样, Cleanup
针对的是⼀个单元测试函数,⽽ TestMain
针对的是同⼀个package所有的测试。
import (
"log"
"os"
"testing"
)
func TestMain(m *testing.M) {
log.Println("do stuff BEFORE the tests!")
exitVal := m.Run()
log.Println("do stuff AFTER the tests!")
os.Exit(exitVal)
}
最后编辑:admin 更新时间:2024-10-22 02:06