Go語(yǔ)言自帶測(cè)試庫(kù)testing使用教程
簡(jiǎn)介
testing是 Go 語(yǔ)言標(biāo)準(zhǔn)庫(kù)自帶的測(cè)試庫(kù)。在 Go 語(yǔ)言中編寫測(cè)試很簡(jiǎn)單,只需要遵循 Go 測(cè)試的幾個(gè)約定,與編寫正常的 Go 代碼沒有什么區(qū)別。Go 語(yǔ)言中有 3 種類型的測(cè)試:?jiǎn)卧獪y(cè)試,性能測(cè)試,示例測(cè)試。下面依次來介紹。
單元測(cè)試
單元測(cè)試又稱為功能性測(cè)試,是為了測(cè)試函數(shù)、模塊等代碼的邏輯是否正確。接下來我們編寫一個(gè)庫(kù),用于將表示羅馬數(shù)字的字符串和整數(shù)互轉(zhuǎn)。羅馬數(shù)字是由M/D/C/L/X/V/I
這幾個(gè)字符根據(jù)一定的規(guī)則組合起來表示一個(gè)正整數(shù):
- M=1000,D=500,C=100,L=50,X=10,V=5,I=1;
- 只能表示 1-3999 范圍內(nèi)的整數(shù),不能表示 0 和負(fù)數(shù),不能表示 4000 及以上的整數(shù),不能表示分?jǐn)?shù)和小數(shù)(當(dāng)然有其他復(fù)雜的規(guī)則來表示這些數(shù)字,這里暫不考慮);
- 每個(gè)整數(shù)只有一種表示方式,一般情況下,連寫的字符表示對(duì)應(yīng)整數(shù)相加,例如
I=1
,II=2
,III=3
。但是,十位字符(I/X/C/M
)最多出現(xiàn) 3 次,所以不能用IIII
表示 4,需要在V
左邊添加一個(gè)I
(即IV
)來表示,不能用VIIII
表示 9,需要使用IX
代替。另外五位字符(V/L/D
)不能連續(xù)出現(xiàn) 2 次,所以不能出現(xiàn)VV
,需要用X
代替。
// roman.go package roman import ( "bytes" "errors" "regexp" ) type romanNumPair struct { Roman string Num int } var ( romanNumParis []romanNumPair romanRegex *regexp.Regexp ) var ( ErrOutOfRange = errors.New("out of range") ErrInvalidRoman = errors.New("invalid roman") ) func init() { romanNumParis = []romanNumPair{ {"M", 1000}, {"CM", 900}, {"D", 500}, {"CD", 400}, {"C", 100}, {"XC", 90}, {"L", 50}, {"XL", 40}, {"X", 10}, {"IX", 9}, {"V", 5}, {"IV", 4}, {"I", 1}, } romanRegex = regexp.MustCompile(`^M{0,3}(CM|CD|D?C{0,3})(XC|XL|L?X{0,3})(IX|IV|V?I{0,3})$`) } func ToRoman(n int) (string, error) { if n <= 0 || n >= 4000 { return "", ErrOutOfRange } var buf bytes.Buffer for _, pair := range romanNumParis { for n > pair.Num { buf.WriteString(pair.Roman) n -= pair.Num } } return buf.String(), nil } func FromRoman(roman string) (int, error) { if !romanRegex.MatchString(roman) { return 0, ErrInvalidRoman } var result int var index int for _, pair := range romanNumParis { for roman[index:index+len(pair.Roman)] == pair.Roman { result += pair.Num index += len(pair.Roman) } } return result, nil }
在 Go 中編寫測(cè)試很簡(jiǎn)單,只需要在待測(cè)試功能所在文件的同級(jí)目錄中創(chuàng)建一個(gè)以_test.go
結(jié)尾的文件。在該文件中,我們可以編寫一個(gè)個(gè)測(cè)試函數(shù)。測(cè)試函數(shù)名必須是TestXxxx
這個(gè)形式,而且Xxxx
必須以大寫字母開頭,另外函數(shù)帶有一個(gè)*testing.T
類型的參數(shù):
// roman_test.go package roman import ( "testing" ) func TestToRoman(t *testing.T) { _, err1 := ToRoman(0) if err1 != ErrOutOfRange { t.Errorf("ToRoman(0) expect error:%v got:%v", ErrOutOfRange, err1) } roman2, err2 := ToRoman(1) if err2 != nil { t.Errorf("ToRoman(1) expect nil error, got:%v", err2) } if roman2 != "I" { t.Errorf("ToRoman(1) expect:%s got:%s", "I", roman2) } }
在測(cè)試函數(shù)中編寫的代碼與正常的代碼沒有什么不同,調(diào)用相應(yīng)的函數(shù),返回結(jié)果,判斷結(jié)果與預(yù)期是否一致,如果不一致則調(diào)用testing.T
的Errorf()
輸出錯(cuò)誤信息。運(yùn)行測(cè)試時(shí),這些錯(cuò)誤信息會(huì)被收集起來,運(yùn)行結(jié)束后統(tǒng)一輸出。
測(cè)試編寫完成之后,使用go test
命令運(yùn)行測(cè)試,輸出結(jié)果:
$ go test
--- FAIL: TestToRoman (0.00s)
roman_test.go:18: ToRoman(1) expect:I got:
FAIL
exit status 1
FAIL github.com/darjun/go-daily-lib/testing 0.172s
我故意將ToRoman()
函數(shù)中寫錯(cuò)了一行代碼,n > pair.Num
中>
應(yīng)該為>=
,單元測(cè)試成功找出了錯(cuò)誤。修改之后重新運(yùn)行測(cè)試:
$ go test PASS ok github.com/darjun/go-daily-lib/testing 0.178s
這次測(cè)試都通過了!
我們還可以給go test
命令傳入-v
選項(xiàng),輸出詳細(xì)的測(cè)試信息:
$ go test -v
=== RUN TestToRoman
--- PASS: TestToRoman (0.00s)
PASS
ok github.com/darjun/go-daily-lib/testing 0.174s
在運(yùn)行每個(gè)測(cè)試函數(shù)前,都輸出一行=== RUN
,運(yùn)行結(jié)束之后輸出--- PASS
或--- FAIL
信息。
表格驅(qū)動(dòng)測(cè)試
在上面的例子中,我們實(shí)際上只測(cè)試了兩種情況,0 和 1。按照這種方式將每種情況都寫出來就太繁瑣了,Go 中流行使用表格的方式將各個(gè)測(cè)試數(shù)據(jù)和結(jié)果列舉出來:
func TestToRoman(t *testing.T) { testCases := []struct { num int expect string err error }{ {0, "", ErrOutOfRange}, {1, "I", nil}, {2, "II", nil}, {3, "III", nil}, {4, "IV", nil}, {5, "V", nil}, {6, "VI", nil}, {7, "VII", nil}, {8, "VIII", nil}, {9, "IX", nil}, {10, "X", nil}, {50, "L", nil}, {100, "C", nil}, {500, "D", nil}, {1000, "M", nil}, {31, "XXXI", nil}, {148, "CXLVIII", nil}, {294, "CCXCIV", nil}, {312, "CCCXII", nil}, {421, "CDXXI", nil}, {528, "DXXVIII", nil}, {621, "DCXXI", nil}, {782, "DCCLXXXII", nil}, {870, "DCCCLXX", nil}, {941, "CMXLI", nil}, {1043, "MXLIII", nil}, {1110, "MCX", nil}, {1226, "MCCXXVI", nil}, {1301, "MCCCI", nil}, {1485, "MCDLXXXV", nil}, {1509, "MDIX", nil}, {1607, "MDCVII", nil}, {1754, "MDCCLIV", nil}, {1832, "MDCCCXXXII", nil}, {1993, "MCMXCIII", nil}, {2074, "MMLXXIV", nil}, {2152, "MMCLII", nil}, {2212, "MMCCXII", nil}, {2343, "MMCCCXLIII", nil}, {2499, "MMCDXCIX", nil}, {2574, "MMDLXXIV", nil}, {2646, "MMDCXLVI", nil}, {2723, "MMDCCXXIII", nil}, {2892, "MMDCCCXCII", nil}, {2975, "MMCMLXXV", nil}, {3051, "MMMLI", nil}, {3185, "MMMCLXXXV", nil}, {3250, "MMMCCL", nil}, {3313, "MMMCCCXIII", nil}, {3408, "MMMCDVIII", nil}, {3501, "MMMDI", nil}, {3610, "MMMDCX", nil}, {3743, "MMMDCCXLIII", nil}, {3844, "MMMDCCCXLIV", nil}, {3888, "MMMDCCCLXXXVIII", nil}, {3940, "MMMCMXL", nil}, {3999, "MMMCMXCIX", nil}, {4000, "", ErrOutOfRange}, } for _, testCase := range testCases { got, err := ToRoman(testCase.num) if got != testCase.expect { t.Errorf("ToRoman(%d) expect:%s got:%s", testCase.num, testCase.expect, got) } if err != testCase.err { t.Errorf("ToRoman(%d) expect error:%v got:%v", testCase.num, testCase.err, err) } } }
上面將要測(cè)試的每種情況列舉出來,然后針對(duì)每個(gè)整數(shù)調(diào)用ToRoman()
函數(shù),比較返回的羅馬數(shù)字字符串和錯(cuò)誤值是否與預(yù)期的相符。后續(xù)要添加新的測(cè)試用例也很方便。
分組和并行
有時(shí)候?qū)ν粋€(gè)函數(shù)有不同維度的測(cè)試,將這些組合在一起有利于維護(hù)。例如上面對(duì)ToRoman()
函數(shù)的測(cè)試可以分為非法值,單個(gè)羅馬字符和普通 3 種情況。
為了分組,我對(duì)代碼做了一定程度的重構(gòu),首先抽象一個(gè)toRomanCase
結(jié)構(gòu):
type toRomanCase struct { num int expect string err error }
將所有的測(cè)試數(shù)據(jù)劃分到 3 個(gè)組中:
var ( toRomanInvalidCases []toRomanCase toRomanSingleCases []toRomanCase toRomanNormalCases []toRomanCase ) func init() { toRomanInvalidCases = []toRomanCase{ {0, "", roman.ErrOutOfRange}, {4000, "", roman.ErrOutOfRange}, } toRomanSingleCases = []toRomanCase{ {1, "I", nil}, {5, "V", nil}, // ... } toRomanNormalCases = []toRomanCase{ {2, "II", nil}, {3, "III", nil}, // ... } }
然后為了避免代碼重復(fù),抽象一個(gè)運(yùn)行多個(gè)toRomanCase
的函數(shù):
func testToRomanCases(cases []toRomanCase, t *testing.T) { for _, testCase := range cases { got, err := roman.ToRoman(testCase.num) if got != testCase.expect { t.Errorf("ToRoman(%d) expect:%s got:%s", testCase.num, testCase.expect, got) } if err != testCase.err { t.Errorf("ToRoman(%d) expect error:%v got:%v", testCase.num, testCase.err, err) } } }
為每個(gè)分組定義一個(gè)測(cè)試函數(shù):
func testToRomanInvalid(t *testing.T) { testToRomanCases(toRomanInvalidCases, t) } func testToRomanSingle(t *testing.T) { testToRomanCases(toRomanSingleCases, t) } func testToRomanNormal(t *testing.T) { testToRomanCases(toRomanNormalCases, t) }
在原來的測(cè)試函數(shù)中,調(diào)用t.Run()
運(yùn)行不同分組的測(cè)試函數(shù),t.Run()
第一個(gè)參數(shù)為子測(cè)試名,第二個(gè)參數(shù)為子測(cè)試函數(shù):
func TestToRoman(t *testing.T) { t.Run("Invalid", testToRomanInvalid) t.Run("Single", testToRomanSingle) t.Run("Normal", testToRomanNormal) }
運(yùn)行:
$ go test -v
=== RUN TestToRoman
=== RUN TestToRoman/Invalid
=== RUN TestToRoman/Single
=== RUN TestToRoman/Normal
--- PASS: TestToRoman (0.00s)
--- PASS: TestToRoman/Invalid (0.00s)
--- PASS: TestToRoman/Single (0.00s)
--- PASS: TestToRoman/Normal (0.00s)
PASS
ok github.com/darjun/go-daily-lib/testing 0.188s
可以看到,依次運(yùn)行 3 個(gè)子測(cè)試,子測(cè)試名是父測(cè)試名和t.Run()
指定的名字組合而成的,如TestToRoman/Invalid
。
默認(rèn)情況下,這些測(cè)試都是依次順序執(zhí)行的。如果各個(gè)測(cè)試之間沒有聯(lián)系,我們可以讓他們并行以加快測(cè)試速度。方法也很簡(jiǎn)單,在testToRomanInvalid/testToRomanSingle/testToRomanNormal
這 3 個(gè)函數(shù)開始處調(diào)用t.Parallel()
,由于這 3 個(gè)函數(shù)直接調(diào)用了testToRomanCases
,也可以只在testToRomanCases
函數(shù)開頭出添加:
func testToRomanCases(cases []toRomanCase, t *testing.T) { t.Parallel() // ... }
運(yùn)行:
$ go test -v ... --- PASS: TestToRoman (0.00s) --- PASS: TestToRoman/Invalid (0.00s) --- PASS: TestToRoman/Normal (0.00s) --- PASS: TestToRoman/Single (0.00s) PASS ok github.com/darjun/go-daily-lib/testing 0.182s
我們發(fā)現(xiàn)測(cè)試完成的順序并不是我們指定的順序。
另外,這個(gè)示例中我將roman_test.go
文件移到了roman_test
包中,所以需要import "github.com/darjun/go-daily-lib/testing/roman"
。這種方式在測(cè)試包有循環(huán)依賴的情況下非常有用,例如標(biāo)準(zhǔn)庫(kù)中net/http
依賴net/url
,url
的測(cè)試函數(shù)依賴net/http
,如果把測(cè)試放在net/url
包中,那么就會(huì)導(dǎo)致循環(huán)依賴url_test(net/url)
->net/http
->net/url
。這時(shí)可以將url_test
放在一個(gè)獨(dú)立的包中。
主測(cè)試函數(shù)
有一種特殊的測(cè)試函數(shù),函數(shù)名為TestMain()
,接受一個(gè)*testing.M
類型的參數(shù)。這個(gè)函數(shù)一般用于在運(yùn)行所有測(cè)試前執(zhí)行一些初始化邏輯(如創(chuàng)建數(shù)據(jù)庫(kù)鏈接),或所有測(cè)試都運(yùn)行結(jié)束之后執(zhí)行一些清理邏輯(釋放數(shù)據(jù)庫(kù)鏈接)。如果測(cè)試文件中定義了這個(gè)函數(shù),則go test
命令會(huì)直接運(yùn)行這個(gè)函數(shù),否者go test
會(huì)創(chuàng)建一個(gè)默認(rèn)的TestMain()
函數(shù)。這個(gè)函數(shù)的默認(rèn)行為就是運(yùn)行文件中定義的測(cè)試。我們自定義TestMain()
函數(shù)時(shí),也需要手動(dòng)調(diào)用m.Run()
方法運(yùn)行測(cè)試函數(shù),否則測(cè)試函數(shù)不會(huì)運(yùn)行。默認(rèn)的TestMain()
類似下面代碼:
func TestMain(m *testing.M) { os.Exit(m.Run()) }
下面自定義一個(gè)TestMain()
函數(shù),打印go test
支持的選項(xiàng):
func TestMain(m *testing.M) { flag.Parse() flag.VisitAll(func(f *flag.Flag) { fmt.Printf("name:%s usage:%s value:%v\n", f.Name, f.Usage, f.Value) }) os.Exit(m.Run()) }
運(yùn)行:
$ go test -v name:test.bench usage:run only benchmarks matching `regexp` value: name:test.benchmem usage:print memory allocations for benchmarks value:false name:test.benchtime usage:run each benchmark for duration `d` value:1s name:test.blockprofile usage:write a goroutine blocking profile to `file` value: name:test.blockprofilerate usage:set blocking profile `rate` (see runtime.SetBlockProfileRate) value:1 name:test.count usage:run tests and benchmarks `n` times value:1 name:test.coverprofile usage:write a coverage profile to `file` value: name:test.cpu usage:comma-separated `list` of cpu counts to run each test with value: name:test.cpuprofile usage:write a cpu profile to `file` value: name:test.failfast usage:do not start new tests after the first test failure value:false name:test.list usage:list tests, examples, and benchmarks matching `regexp` then exit value: name:test.memprofile usage:write an allocation profile to `file` value: name:test.memprofilerate usage:set memory allocation profiling `rate` (see runtime.MemProfileRate) value:0 name:test.mutexprofile usage:write a mutex contention profile to the named file after execution value: name:test.mutexprofilefraction usage:if >= 0, calls runtime.SetMutexProfileFraction() value:1 name:test.outputdir usage:write profiles to `dir` value: name:test.paniconexit0 usage:panic on call to os.Exit(0) value:true name:test.parallel usage:run at most `n` tests in parallel value:8 name:test.run usage:run only tests and examples matching `regexp` value: name:test.short usage:run smaller test suite to save time value:false name:test.testlogfile usage:write test action log to `file` (for use only by cmd/go) value: name:test.timeout usage:panic test binary after duration `d` (default 0, timeout disabled) value:10m0s name:test.trace usage:write an execution trace to `file` value: name:test.v usage:verbose: print additional output value:tru
這些選項(xiàng)也可以通過go help testflag
查看。
其他
另一個(gè)函數(shù)FromRoman()
我沒有寫任何測(cè)試,就交給大家了??
性能測(cè)試
性能測(cè)試是為了對(duì)函數(shù)的運(yùn)行性能進(jìn)行評(píng)測(cè)。性能測(cè)試也必須在_test.go
文件中編寫,且函數(shù)名必須是BenchmarkXxxx
開頭。性能測(cè)試函數(shù)接受一個(gè)*testing.B
的參數(shù)。下面我們編寫 3 個(gè)計(jì)算第 n 個(gè)斐波那契數(shù)的函數(shù)。
第一種方式:遞歸
func Fib1(n int) int { if n <= 1 { return n } return Fib1(n-1) + Fib1(n-2) }
第二種方式:備忘錄
func fibHelper(n int, m map[int]int) int { if n <= 1 { return n } if v, ok := m[n]; ok { return v } v := fibHelper(n-2, m) + fibHelper(n-1, m) m[n] = v return v } func Fib2(n int) int { m := make(map[int]int) return fibHelper(n, m) }
第三種方式:迭代
func Fib3(n int) int { if n <= 1 { return n } f1, f2 := 0, 1 for i := 2; i <= n; i++ { f1, f2 = f2, f1+f2 } return f2 }
下面我們來測(cè)試這 3 個(gè)函數(shù)的執(zhí)行效率:
// fib_test.go func BenchmarkFib1(b *testing.B) { for i := 0; i < b.N; i++ { Fib1(20) } } func BenchmarkFib2(b *testing.B) { for i := 0; i < b.N; i++ { Fib2(20) } } func BenchmarkFib3(b *testing.B) { for i := 0; i < b.N; i++ { Fib3(20) } }
需要特別注意的是N
,go test
會(huì)一直調(diào)整這個(gè)數(shù)值,直到測(cè)試時(shí)間能得出可靠的性能數(shù)據(jù)為止。運(yùn)行:
$ go test -bench=. goos: windows goarch: amd64 pkg: github.com/darjun/go-daily-lib/testing/fib cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz BenchmarkFib1-8 31110 39144 ns/op BenchmarkFib2-8 582637 3127 ns/op BenchmarkFib3-8 191600582 5.588 ns/op PASS ok github.com/darjun/go-daily-lib/testing/fib 5.225s
性能測(cè)試默認(rèn)不會(huì)執(zhí)行,需要通過-bench=.
指定運(yùn)行。-bench
選項(xiàng)的值是一個(gè)簡(jiǎn)單的模式,.
表示匹配所有的,Fib
表示運(yùn)行名字中有Fib
的。
上面的測(cè)試結(jié)果表示Fib1
在指定時(shí)間內(nèi)執(zhí)行了 31110 次,平均每次 39144 ns,Fib2
在指定時(shí)間內(nèi)運(yùn)行了 582637 次,平均每次耗時(shí) 3127 ns,Fib3
在指定時(shí)間內(nèi)運(yùn)行了 191600582 次,平均每次耗時(shí) 5.588 ns。
其他選項(xiàng)
有一些選項(xiàng)可以控制性能測(cè)試的執(zhí)行。
-benchtime
:設(shè)置每個(gè)測(cè)試的運(yùn)行時(shí)間。
$ go test -bench=. -benchtime=30s
運(yùn)行了更長(zhǎng)的時(shí)間:
$ go test -bench=. -benchtime=30s goos: windows goarch: amd64 pkg: github.com/darjun/go-daily-lib/testing/fib cpu: Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz BenchmarkFib1-8 956464 38756 ns/op BenchmarkFib2-8 17862495 2306 ns/op BenchmarkFib3-8 1000000000 5.591 ns/op PASS ok github.com/darjun/go-daily-lib/testing/fib 113.498s
-benchmem
:輸出性能測(cè)試函數(shù)的內(nèi)存分配情況。
-memprofile file
:將內(nèi)存分配數(shù)據(jù)寫入文件。
-cpuprofile file
:將 CPU 采樣數(shù)據(jù)寫入文件,方便使用go tool pprof
工具分析,詳見我的另一篇文章《你不知道的 Go 之 pprof》
運(yùn)行:
$ go test -bench=. -benchtime=10s -cpuprofile=./cpu.prof -memprofile=./mem.prof goos: windows goarch: amd64 pkg: github.com/darjun/fib BenchmarkFib1-16 356006 33423 ns/op BenchmarkFib2-16 8958194 1340 ns/op BenchmarkFib3-16 1000000000 6.60 ns/op PASS ok github.com/darjun/fib 33.321s
同時(shí)生成了 CPU 采樣數(shù)據(jù)和內(nèi)存分配數(shù)據(jù),通過go tool pprof
分析:
$ go tool pprof ./cpu.prof Type: cpu Time: Aug 4, 2021 at 10:21am (CST) Duration: 32.48s, Total samples = 36.64s (112.81%) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top10 Showing nodes accounting for 29640ms, 80.90% of 36640ms total Dropped 153 nodes (cum <= 183.20ms) Showing top 10 nodes out of 74 flat flat% sum% cum cum% 11610ms 31.69% 31.69% 11620ms 31.71% github.com/darjun/fib.Fib1 6490ms 17.71% 49.40% 6680ms 18.23% github.com/darjun/fib.Fib3 2550ms 6.96% 56.36% 8740ms 23.85% runtime.mapassign_fast64 2050ms 5.59% 61.95% 2060ms 5.62% runtime.stdcall2 1620ms 4.42% 66.38% 2140ms 5.84% runtime.mapaccess2_fast64 1480ms 4.04% 70.41% 12350ms 33.71% github.com/darjun/fib.fibHelper 1480ms 4.04% 74.45% 2960ms 8.08% runtime.evacuate_fast64 1050ms 2.87% 77.32% 1050ms 2.87% runtime.memhash64 760ms 2.07% 79.39% 760ms 2.07% runtime.stdcall7 550ms 1.50% 80.90% 7230ms 19.73% github.com/darjun/fib.BenchmarkFib3 (pprof)
內(nèi)存:
$ go tool pprof ./mem.prof Type: alloc_space Time: Aug 4, 2021 at 10:30am (CST) Entering interactive mode (type "help" for commands, "o" for options) (pprof) top10 Showing nodes accounting for 8.69GB, 100% of 8.69GB total Dropped 12 nodes (cum <= 0.04GB) flat flat% sum% cum cum% 8.69GB 100% 100% 8.69GB 100% github.com/darjun/fib.fibHelper 0 0% 100% 8.69GB 100% github.com/darjun/fib.BenchmarkFib2 0 0% 100% 8.69GB 100% github.com/darjun/fib.Fib2 (inline) 0 0% 100% 8.69GB 100% testing.(*B).launch 0 0% 100% 8.69GB 100% testing.(*B).runN (pprof)
示例測(cè)試
示例測(cè)試用于演示模塊或函數(shù)的使用。同樣地,示例測(cè)試也在文件_test.go
中編寫,并且示例測(cè)試函數(shù)名必須是ExampleXxx
的形式。在Example*
函數(shù)中編寫代碼,然后在注釋中編寫期望的輸出,go test
會(huì)運(yùn)行該函數(shù),然后將實(shí)際輸出與期望的做比較。下面摘取自 Go 源碼net/url/example_test.go
文件中的代碼演示了url.Values
的用法:
func ExampleValuesGet() { v := url.Values{} v.Set("name", "Ava") v.Add("friend", "Jess") v.Add("friend", "Sarah") v.Add("friend", "Zoe") fmt.Println(v.Get("name")) fmt.Println(v.Get("friend")) fmt.Println(v["friend"]) // Output: // Ava // Jess // [Jess Sarah Zoe] }
注釋中Output:
后是期望的輸出結(jié)果,go test
會(huì)運(yùn)行這些函數(shù)并與期望的結(jié)果做比較,比較會(huì)忽略空格。
有時(shí)候我們輸出的順序是不確定的,這時(shí)就需要使用Unordered Output
。我們知道url.Values
底層類型為map[string][]string
,所以可以遍歷輸出所有的鍵值,但是輸出順序不確定:
func ExampleValuesAll() { v := url.Values{} v.Set("name", "Ava") v.Add("friend", "Jess") v.Add("friend", "Sarah") v.Add("friend", "Zoe") for key, values := range v { fmt.Println(key, values) } // Unordered Output: // name [Ava] // friend [Jess Sarah Zoe] }
運(yùn)行:
$ go test -v $ go test -v
=== RUN ExampleValuesGet
--- PASS: ExampleValuesGet (0.00s)
=== RUN ExampleValuesAll
--- PASS: ExampleValuesAll (0.00s)
PASS
ok github.com/darjun/url 0.172s
沒有注釋,或注釋中無Output/Unordered Output
的函數(shù)會(huì)被忽略。
總結(jié)
本文介紹了 Go 中的 3 種測(cè)試:?jiǎn)卧獪y(cè)試,性能測(cè)試和示例測(cè)試。為了讓程序更可靠,讓以后的重構(gòu)更安全、更放心,單元測(cè)試必不可少。排查程序中的性能問題,性能測(cè)試能派上大用場(chǎng)。示例測(cè)試主要是為了演示如何使用某個(gè)功能。
參考
- testing 官方文檔: https://golang.google.cn/pkg/testing/
- Go 每日一庫(kù) GitHub:https://github.com/darjun/go-daily-lib
以上就是Go語(yǔ)言自帶測(cè)試庫(kù)testing使用教程的詳細(xì)內(nèi)容,更多關(guān)于Go語(yǔ)言測(cè)試庫(kù)testing的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
詳解Gotorch多機(jī)定時(shí)任務(wù)管理系統(tǒng)
遵循著“學(xué)一門語(yǔ)言最好的方式是使用它”的理念,想著用Go來實(shí)現(xiàn)些什么,剛好有一個(gè)比較讓我煩惱的問題,于是用Go解決一下,即使不在生產(chǎn)環(huán)境使用,也可以作為Go語(yǔ)言學(xué)習(xí)的一種方式。2021-05-05golang調(diào)用shell命令(實(shí)時(shí)輸出,終止)
本文主要介紹了golang調(diào)用shell命令(實(shí)時(shí)輸出,終止),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-02-02Golang 協(xié)程配合管道的實(shí)現(xiàn)示例
本文主要介紹了Golang協(xié)程配合管道的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-01-01VSCode Golang dlv調(diào)試數(shù)據(jù)截?cái)鄦栴}及處理方法
這篇文章主要介紹了VSCode Golang dlv調(diào)試數(shù)據(jù)截?cái)鄦栴},本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-06-06GoLang jwt無感刷新與SSO單點(diǎn)登錄限制解除方法詳解
這篇文章主要介紹了GoLang jwt無感刷新與SSO單點(diǎn)登錄限制解除方法,JWT是一個(gè)簽名的JSON對(duì)象,通常用作Oauth2的Bearer token,JWT包括三個(gè)用.分割的部分。本文將利用JWT進(jìn)行認(rèn)證和加密,感興趣的可以了解一下2023-03-03重學(xué)Go語(yǔ)言之運(yùn)算符與控制結(jié)構(gòu)詳解
對(duì)于任何編程語(yǔ)言來說,運(yùn)算符和控制結(jié)構(gòu)都算是最基礎(chǔ)的知識(shí)了,既然是基礎(chǔ),當(dāng)然非常有必要學(xué)習(xí),因此在這篇文章中我們就來討論一下2023-02-02