<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          Go 每日一庫之 testing

          共 10330字,需瀏覽 21分鐘

           ·

          2021-11-15 08:01

          簡介

          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=1II=2III=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.TErrorf()輸出錯(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/urlurl的測(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)
          ??}
          }

          需要特別注意的是Ngo 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??

          參考

          1. testing 官方文檔: https://golang.google.cn/pkg/testing/
          2. Go 每日一庫 GitHub:https://github.com/darjun/go-daily-lib


          推薦閱讀


          福利

          我為大家整理了一份從入門到進(jìn)階的Go學(xué)習(xí)資料禮包,包含學(xué)習(xí)建議:入門看什么,進(jìn)階看什么。關(guān)注公眾號(hào) 「polarisxu」,回復(fù)?ebook?獲取;還可以回復(fù)「進(jìn)群」,和數(shù)萬 Gopher 交流學(xué)習(xí)。

          瀏覽 19
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  影音先锋成人在线麻豆 | 看黄在线免费观看 | 天津人妻第三次3p视频 | 免费crm一区二区 | 欧美三级毛片 |