<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
testing是 Go 語言標準庫自帶的測試庫。在 Go 語言中編寫測試很簡單,只需要遵循 Go 測試的幾個約定,與編寫正常的 Go 程式碼沒有什麼區別。Go 語言中有 3 種型別的測試:單元測試,效能測試,範例測試。下面依次來介紹。
單元測試又稱為功能性測試,是為了測試函數、模組等程式碼的邏輯是否正確。接下來我們編寫一個庫,用於將表示羅馬數位的字串和整數互轉。羅馬數位是由M/D/C/L/X/V/I
這幾個字元根據一定的規則組合起來表示一個正整數:
I=1
,II=2
,III=3
。但是,十位字元(I/X/C/M
)最多出現 3 次,所以不能用IIII
表示 4,需要在V
左邊新增一個I
(即IV
)來表示,不能用VIIII
表示 9,需要使用IX
代替。另外五位字元(V/L/D
)不能連續出現 2 次,所以不能出現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 中編寫測試很簡單,只需要在待測試功能所在檔案的同級目錄中建立一個以_test.go
結尾的檔案。在該檔案中,我們可以編寫一個個測試函數。測試函數名必須是TestXxxx
這個形式,而且Xxxx
必須以大寫字母開頭,另外函數帶有一個*testing.T
型別的引數:
// 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) } }
在測試函數中編寫的程式碼與正常的程式碼沒有什麼不同,呼叫相應的函數,返回結果,判斷結果與預期是否一致,如果不一致則呼叫testing.T
的Errorf()
輸出錯誤資訊。執行測試時,這些錯誤資訊會被收集起來,執行結束後統一輸出。
測試編寫完成之後,使用go test
命令執行測試,輸出結果:
$ 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()
函數中寫錯了一行程式碼,n > pair.Num
中>
應該為>=
,單元測試成功找出了錯誤。修改之後重新執行測試:
$ go test PASS ok github.com/darjun/go-daily-lib/testing 0.178s
這次測試都通過了!
我們還可以給go test
命令傳入-v
選項,輸出詳細的測試資訊:
$ go test -v
=== RUN TestToRoman
--- PASS: TestToRoman (0.00s)
PASS
ok github.com/darjun/go-daily-lib/testing 0.174s
在執行每個測試函數前,都輸出一行=== RUN
,執行結束之後輸出--- PASS
或--- FAIL
資訊。
在上面的例子中,我們實際上只測試了兩種情況,0 和 1。按照這種方式將每種情況都寫出來就太繁瑣了,Go 中流行使用表格的方式將各個測試資料和結果列舉出來:
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) } } }
上面將要測試的每種情況列舉出來,然後針對每個整數呼叫ToRoman()
函數,比較返回的羅馬數位字串和錯誤值是否與預期的相符。後續要新增新的測試用例也很方便。
有時候對同一個函數有不同維度的測試,將這些組合在一起有利於維護。例如上面對ToRoman()
函數的測試可以分為非法值,單個羅馬字元和普通 3 種情況。
為了分組,我對程式碼做了一定程度的重構,首先抽象一個toRomanCase
結構:
type toRomanCase struct { num int expect string err error }
將所有的測試資料劃分到 3 個組中:
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}, // ... } }
然後為了避免程式碼重複,抽象一個執行多個toRomanCase
的函數:
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) } } }
為每個分組定義一個測試函數:
func testToRomanInvalid(t *testing.T) { testToRomanCases(toRomanInvalidCases, t) } func testToRomanSingle(t *testing.T) { testToRomanCases(toRomanSingleCases, t) } func testToRomanNormal(t *testing.T) { testToRomanCases(toRomanNormalCases, t) }
在原來的測試函數中,呼叫t.Run()
執行不同分組的測試函數,t.Run()
第一個引數為子測試名,第二個引數為子測試函數:
func TestToRoman(t *testing.T) { t.Run("Invalid", testToRomanInvalid) t.Run("Single", testToRomanSingle) t.Run("Normal", testToRomanNormal) }
執行:
$ 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
可以看到,依次執行 3 個子測試,子測試名是父測試名和t.Run()
指定的名字組合而成的,如TestToRoman/Invalid
。
預設情況下,這些測試都是依次順序執行的。如果各個測試之間沒有聯絡,我們可以讓他們並行以加快測試速度。方法也很簡單,在testToRomanInvalid/testToRomanSingle/testToRomanNormal
這 3 個函數開始處呼叫t.Parallel()
,由於這 3 個函數直接呼叫了testToRomanCases
,也可以只在testToRomanCases
函數開頭出新增:
func testToRomanCases(cases []toRomanCase, t *testing.T) { t.Parallel() // ... }
執行:
$ 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
我們發現測試完成的順序並不是我們指定的順序。
另外,這個範例中我將roman_test.go
檔案移到了roman_test
包中,所以需要import "github.com/darjun/go-daily-lib/testing/roman"
。這種方式在測試包有迴圈依賴的情況下非常有用,例如標準庫中net/http
依賴net/url
,url
的測試函數依賴net/http
,如果把測試放在net/url
包中,那麼就會導致迴圈依賴url_test(net/url)
->net/http
->net/url
。這時可以將url_test
放在一個獨立的包中。
有一種特殊的測試函數,函數名為TestMain()
,接受一個*testing.M
型別的引數。這個函數一般用於在執行所有測試前執行一些初始化邏輯(如建立資料庫連結),或所有測試都執行結束之後執行一些清理邏輯(釋放資料庫連結)。如果測試檔案中定義了這個函數,則go test
命令會直接執行這個函數,否者go test
會建立一個預設的TestMain()
函數。這個函數的預設行為就是執行檔案中定義的測試。我們自定義TestMain()
函數時,也需要手動呼叫m.Run()
方法執行測試函數,否則測試函數不會執行。預設的TestMain()
類似下面程式碼:
func TestMain(m *testing.M) { os.Exit(m.Run()) }
下面自定義一個TestMain()
函數,列印go test
支援的選項:
func TestMain(m *testing.M) { flag.Parse() flag.VisitAll(func(f *flag.Flag) { fmt.Printf("name:%s usage:%s value:%vn", f.Name, f.Usage, f.Value) }) os.Exit(m.Run()) }
執行:
$ 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
這些選項也可以通過go help testflag
檢視。
另一個函數FromRoman()
我沒有寫任何測試,就交給大家了
相關文章
<em>Mac</em>Book项目 2009年学校开始实施<em>Mac</em>Book项目,所有师生配备一本<em>Mac</em>Book,并同步更新了校园无线网络。学校每周进行电脑技术更新,每月发送技术支持资料,极大改变了教学及学习方式。因此2011
2021-06-01 09:32:01
综合看Anker超能充系列的性价比很高,并且与不仅和iPhone12/苹果<em>Mac</em>Book很配,而且适合多设备充电需求的日常使用或差旅场景,不管是安卓还是Switch同样也能用得上它,希望这次分享能给准备购入充电器的小伙伴们有所
2021-06-01 09:31:42
除了L4WUDU与吴亦凡已经多次共事,成为了明面上的厂牌成员,吴亦凡还曾带领20XXCLUB全队参加2020年的一场音乐节,这也是20XXCLUB首次全员合照,王嗣尧Turbo、陈彦希Regi、<em>Mac</em> Ova Seas、林渝植等人全部出场。然而让
2021-06-01 09:31:34
目前应用IPFS的机构:1 谷歌<em>浏览器</em>支持IPFS分布式协议 2 万维网 (历史档案博物馆)数据库 3 火狐<em>浏览器</em>支持 IPFS分布式协议 4 EOS 等数字货币数据存储 5 美国国会图书馆,历史资料永久保存在 IPFS 6 加
2021-06-01 09:31:24
开拓者的车机是兼容苹果和<em>安卓</em>,虽然我不怎么用,但确实兼顾了我家人的很多需求:副驾的门板还配有解锁开关,有的时候老婆开车,下车的时候偶尔会忘记解锁,我在副驾驶可以自己开门:第二排设计很好,不仅配置了一个很大的
2021-06-01 09:30:48
不仅是<em>安卓</em>手机,苹果手机的降价力度也是前所未有了,iPhone12也“跳水价”了,发布价是6799元,如今已经跌至5308元,降价幅度超过1400元,最新定价确认了。iPhone12是苹果首款5G手机,同时也是全球首款5nm芯片的智能机,它
2021-06-01 09:30:45