Go语言 测试练习
Go工程化的语言供简便 实用 单元测试能力强
测试 Hello Test 在$GOPATH/src 目录下创建hello目录 示例代码根目录
建文件hello.go 函数 hello()功能 返回由单词拼接成句子
package hello
func hello() string {
words := []string{"hello", "func", "in", "package", "hello"}
wl := len(words)
sentence := ""
for key, word := range words {
sentence += word
if key < wl-1 {
sentence += " "
} else {
sentence += "."
}
}
return sentence
}
建文件hello_test.go内容
package hello
import (
"fmt"
"testing"
)
func TestHello(t *testing.T) {
got := hello()
expect := "hello func in package hello."
if got != expect {
t.Errorf("got [%s] expected [%s]", got, expect)
}
}
func BenchmarkHello(b *testing.B) {
for i := 0; i < b.N; i++ {
hello()
}
}
func ExampleHello() {
hl := hello()
fmt.Println(hl) // Output: hello func in package hello.
}
打开终端 进入hello目录 命令 go test
c:/go/bin/go.exe test -v [F:/user/go/src/hello]
PASS
ok 0.665s
Go测试代码 名称以_test.go结尾的
测试代码包含[测试函数 测试辅助代码 示例函数]
测试函数
以Test开头的是功能测试函数
以Benchmark开头的是性能测试函数
测试辅助代码
为测试函数服务的公共函数 初始化函数 测试数据等
示例函数
以Example开头的说明 被测试函数用法的函数
测试代码作为包的一部分
可以访问包中不可导出的元素
但需要的时候(如避免循环依赖)可修改测试文件的包名
如package hello的测试文件 包名可设为 package hello_test
功能测试函数
要接收 *testing.T 类型的单一参数t *testing.T 类型管理 测试状态和支持格式化的测试日志
测试日志在测试执行过程中积累 完成后输出到标准错误输出
Go标准库testing.T 类型的常用方法的用法
测试函数中的某条测试用例 执行结果与预期不符时
调用 t.Error()或t.Errorf()方法 记录日志 并标记测试失败
#go/src/bytes/compare_test.go
func TestCompareIdenticalSlice(t *testing.T) {
var b = []byte("Hello Gophers!")
if Compare(b, b) != 0 {
t.Error("b != b")
}
if Compare(b, b[:1]) != 1 {
t.Error("b > b[:1] failed")
}
}
t.Fatal和t.Fatalf方法 失败跳出该测试函数
# go/src/bytes/reader_test.gofunc TestReadAfterBigSeek(t *testing.T) {
r := NewReader([]byte("0123456789"))
if _, err := r.Seek(1<<31+5, os.SEEK_SET); err != nil {
t.Fatal(err)
}
if n, err := r.Read(make([]byte, 10)); n != 0 || err != io.EOF {
t.Errorf("Read = %d, %v; want 0, EOF", n, err)
}
}
t.Skip和t.Skipf方法 跳过某条测试用例
#go/src/archive/zip/zip_test.gofunc TestZip64(t *testing.T) {
if testing.Short() {
t.Skip("slow test; skipping")
}
const size = 1 << 32 // before the "END\n" part
buf := testZip64(t, size)
testZip64DirectoryRecordLength(buf, t)
}
测试t.Log和 t.Logf记录日志
#go/src/regexp/exec_test.gofunc TestFowler(t *testing.T) {
files, err := filepath.Glob("testdata[A-Za-z0-9_\-]+\.go:` + Rline
Rshortfile = `[A-Za-z0-9_\-]+\.go:` + Rline
)
var tests = []tester{ // individual pieces:
{0, "", ""},
{0, "XXX", "XXX"},
{Ldate, "", Rdate + " "},
{Ltime, "", Rtime + " "},
{Ltime | Lmicroseconds, "", Rtime + Rmicroseconds + " "},
{Lmicroseconds, "", Rtime + Rmicroseconds + " "}, // microsec implies time
{Llongfile, "", Rlongfile + " "},
{Lshortfile, "", Rshortfile + " "},
{Llongfile | Lshortfile, "", Rshortfile + " "}, // shortfile overrides longfile
// everything at once:
{Ldate | Ltime | Lmicroseconds | Llongfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rlongfile + " "},
{Ldate | Ltime | Lmicroseconds | Lshortfile, "XXX", "XXX" + Rdate + " " + Rtime + Rmicroseconds + " " + Rshortfile + " "},
}
测试代码中也能定义init函数
init函数会在引入外部包 定义常量 声明变量 后被自动调用
可在init函数里编写测试 的初始化代码
#go/src/bytes/buffer_test.go
func init() {
testBytes = make([]byte, N)
for i := 0; i < N; i++ {
testBytes[i] = 'a' + byte(i%26)
}
data = string(testBytes)
}
封装测试专用函数 抽象测试结构体
等#go/src/log/log_test.go:
type tester struct {
flag int
prefix string
pattern string // regexp that log output must match; we add ^ and expected_text$ always
}
func testPrint(t *testing.T, flag int, prefix string, pattern string, useFormat bool) {
// ...
}
无需接收参数 但需用注释Output:标记说明示例函数的输出值
未指定Output:标记或输出值为空的示例不被执行
示例函数需要归属于某个 包/函数/类型/类型 的方法 具体命名规则如下
func Example() { ... } # 包的示例函数
func ExampleF() { ... } # 函数F的示例函数
func ExampleT() { ... } # 类型T的示例函数
func ExampleT_M() { ... } # 类型T的M方法的示例函数
# 多示例函数 需要跟下划线加小写字母开头的后缀
func Example_suffix() { ... }
func ExampleF_suffix() { ... }
func ExampleT_suffix() { ... }
func ExampleT_M_suffix() { ... }
go doc 工具会 解析 示例函数 的 函数体作为对应 包/函数/类型/类型的方法 的用法
测试函数的相关说明 可以通过 go help testfunc 看帮助文档
使用 go test 工具
go test执行测试shell端进入测试包的目录
go test或go test $pkg_name_in_gopath 即对指定的包测试
如go test github.com/tabalt/... 的命令
是测试$GOPATH/github.com/tabalt/ 目录下所有的项目
go test std 则 执行Golang标准库的所有测试
查看测试结果 -v 参数
go test -v只执行其中的某些函数 可用-run 参数
正则表达式 匹配 要执行的功能测试函数名
指定参数后 其他功能测试函数TestHello不执行
go test -v -run=xxx
性能测试 加-bench参数
性能测试函数默认不执行 添加-bench参数 并指定匹配性能测试函数名的正则表达式如要执行 包中所有的性能测试函数 -bench . 或 -bench=.
go test -bench=.
PASS
ok hello 2.772s
查看 性能测试时的内存情况 添加参数 -benchmem
go test -bench=. -benchmem
PASS
BenchmarkHello-8 2000000 666 ns/op 208 B/op 9 allocs/op
ok hello 2.014s
参数 -cover 查看 编写的测试对代码的覆盖率
详细覆盖率信息 -coverprofile 输出到文件 并用 go tool cover 来查看
用法参考 go tool cover -help
go test 命令的参数及用法
通过go help testflag 查看
高级测试技术
IO相关测试testing/iotest包 实现了常用的出错 Reader和Writer 可供 在io相关的测试中使用
触发数据错误dataErrReader 通过DataErrReader()函数创建
读取一半内容 halfReader 通过HalfReader()函数创建
读取一个byte的oneByteReader 通过OneByteReader()函数创建
触发超时错误的timeoutReader 通过TimeoutReader()函数创建
写入指定位数内容后停止 truncateWriter 通过TruncateWriter()函数创建
读取时记录日志 readLogger 通过NewReadLogger()函数创建
写入时记录日志 writeLogger 通过NewWriteLogger()函数创建
黑盒测试
testing/quick包实现了帮助黑盒测试的实用函数 Check和CheckEqualCheck 函数的第1个参数是要测试的 只返回bool值的黑盒函数
f Check会为f的每个参数设置任意值并多次调用
如果f返回false Check函数会返回错误值 *CheckError
Check函数的第2个参数
可指定quick.Config类型
config传nil则默认用quick.defaultConfig
quick.Config结构体包含测试运行选项
# go/src/math/big/int_test.go
func checkMul(a, b []byte) bool {
var x, y, z1 Int
x.SetBytes(a)
y.SetBytes(b)
z1.Mul(&x, &y)
var z2 Int
z2.SetBytes(mulBytes(a, b))
return z1.Cmp(&z2) == 0
}
func TestMul(t *testing.T) {
if err := quick.Check(checkMul, nil); err != nil {
t.Error(err)
}
}
CheckEqual函数是比较给定的两个黑盒函数是否相等 函数原型如下
func CheckEqual(f, g interface{}, config *Config) (err error)
HTTP测试
net/http/httptest包提供了HTTP相关代码的工具的测试代码可创建临时的httptest.Server来测试发送HTTP请求的代码
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
}))
defer ts.Close()
res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := ioutil.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", greeting)
创建应答的记录器httptest.ResponseRecorder来检测应答内容
handler := func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "something failed", http.StatusInternalServerError)
}
req, err := http.NewRequest("GET", "http://example.com/foo", nil)
if err != nil {
log.Fatal(err)
}
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("%d - %s", w.Code, w.Body.String())
测试进程操作行为
当被测函数有操作进程的行为 可将被测程序作为一个子进程执行测试func Crasher() {//被测试的进程退出函数
fmt.Println("Going down in flames!")
os.Exit(1)
}
//测试进程退出函数的测试函数
func TestCrasher(t *testing.T) {
if os.Getenv("BE_CRASHER") == "1" {
Crasher()
return
}
cmd := exec.Command(os.Args[0], "-test.run=TestCrasher")
cmd.Env = append(os.Environ(), "BE_CRASHER=1")
err := cmd.Run()
if e, ok := err.(*exec.ExitError); ok && !e.Success() {
return
}
t.Fatalf("process ran with err %v, want exit status 1", err)
}
尊贵的董事大人
英文标题不为空时 视为本栏投稿
需要关键字 描述 英文标题