Go 每日一庫之 testing
簡介
testing是 Go 語言標(biāo)準(zhǔn)庫自帶的測(cè)試庫。在 Go 語言中編寫測(cè)試很簡單,只需要遵循 Go 測(cè)試的幾個(gè)約定,與編寫正常的 Go 代碼沒有什么區(qū)別。Go 語言中有 3 種類型的測(cè)試:單元測(cè)試,性能測(cè)試,示例測(cè)試。下面依次來介紹。
單元測(cè)試
單元測(cè)試又稱為功能性測(cè)試,是為了測(cè)試函數(shù)、模塊等代碼的邏輯是否正確。接下來我們編寫一個(gè)庫,用于將表示羅馬數(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è)試很簡單,只需要在待測(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è)試速度。方法也很簡單,在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)庫中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ù)庫鏈接),或所有測(cè)試都運(yùn)行結(jié)束之后執(zhí)行一些清理邏輯(釋放數(shù)據(jù)庫鏈接)。如果測(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?????Fib1(20)
??}
}
func?BenchmarkFib2(b?*testing.B)?{
??for?i?:=?0;?i?????Fib2(20)
??}
}
func?BenchmarkFib3(b?*testing.B)?{
??for?i?:=?0;?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è)簡單的模式,.表示匹配所有的,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)行了更長的時(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è)試:單元測(cè)試,性能測(cè)試和示例測(cè)試。為了讓程序更可靠,讓以后的重構(gòu)更安全、更放心,單元測(cè)試必不可少。排查程序中的性能問題,性能測(cè)試能派上大用場(chǎng)。示例測(cè)試主要是為了演示如何使用某個(gè)功能。
大家如果發(fā)現(xiàn)好玩、好用的 Go 語言庫,歡迎到 Go 每日一庫 GitHub 上提交 issue??
參考
testing 官方文檔: https://golang.google.cn/pkg/testing/ Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib
推薦閱讀
