Go 程序测试基础知识
单元测试,它又称程序员测试。顾名思义,这就是程序员们本该做的自我检查工作之一。
可以为 Go 程序编写三类测试,即:功能测试(test)、基准测试(benchmark,也称性能测试),以及示例测试(example)。
对于前两类测试,从名称上你就应该可以猜到它们的用途。而示例测试严格来讲也是一种功能测试,只不过它更关注程序打印出来的内容。
一般情况下,一个测试源码文件只会针对于某个命令源码文件,或库源码文件(以下简称被测源码文件)做测试,所以我们总会(并且应该)把它们放在同一个代码包内。
测试源码文件的主名称应该以被测源码文件的主名称为前导,并且必须以“_test”为后缀。例如,如果被测源码文件的名称为 ewma.go,那么针对它的测试源码文件的名称就应该是 ewma_test.go。
Go语言对测试函数的名称和签名的三个规定。
- 对于功能测试函数来说,其名称必须以Test为前缀,并且参数列表中只应有一个*testing.T类型的参数声明。
 - 对于性能测试函数来说,其名称必须以Benchmark为前缀,并且唯一参数的类型必须是*testing.B类型的。
 - 对于示例测试函数来说,其名称必须以Example为前缀,但对函数的参数列表没有强制规定。
 
go test 命令
go test 命令在开始运行时,会先做一些准备工作,比如,确定内部需要用到的命令,检查我们指定的代码包或源码文件的有效性,以及判断我们给予的标记是否合法,等等。
在准备工作顺利完成之后,go test 命令就会针对每个被测代码包,依次地进行构建、执行包中符合要求的测试函数,清理临时文件,打印测试结果。这就是通常情况下的主要测试流程。
请注意上述的“依次”二字。对于每个被测代码包,go test 命令会串行地执行测试流程中的每个步骤。
但是,为了加快测试速度,它通常会并发地对多个被测代码包进行功能测试,只不过,在最后打印测试结果的时候,它会依照我们给定的顺序逐个进行,这会让我们感觉到它是在完全串行地执行测试流程。
另一方面,由于并发的测试会让性能测试的结果存在偏差,所以性能测试一般都是串行进行的。更具体地说,只有在所有构建步骤都做完之后,go test 命令才会真正地开始进行性能测试。
并且,下一个代码包性能测试的进行,总会等到上一个代码包性能测试的结果打印完成才会开始,而且性能测试函数的执行也都会是串行的。
示例
ewma.go
package mainimport ("math""sync""sync/atomic")// UseNilMetrics is checked by the constructor functions for all of the// standard metrics. If it is true, the metric returned is a stub.//// This global kill-switch helps quantify the observer effect and makes// for less cluttered pprof profiles.var UseNilMetrics bool = false// EWMAs continuously calculate an exponentially-weighted moving average// based on an outside source of clock ticks.type EWMA interface {Rate() float64Snapshot() EWMATick()Update(int64)}// NewEWMA constructs a new EWMA with the given alpha.func NewEWMA(alpha float64) EWMA {if UseNilMetrics {return NilEWMA{}}return &StandardEWMA{alpha: alpha}}// NewEWMA1 constructs a new EWMA for a one-minute moving average.func NewEWMA1() EWMA {return NewEWMA(1 - math.Exp(-5.0/60.0/1))}// NewEWMA5 constructs a new EWMA for a five-minute moving average.func NewEWMA5() EWMA {return NewEWMA(1 - math.Exp(-5.0/60.0/5))}// NewEWMA15 constructs a new EWMA for a fifteen-minute moving average.func NewEWMA15() EWMA {return NewEWMA(1 - math.Exp(-5.0/60.0/15))}// EWMASnapshot is a read-only copy of another EWMA.type EWMASnapshot float64// Rate returns the rate of events per second at the time the snapshot was// taken.func (a EWMASnapshot) Rate() float64 { return float64(a) }// Snapshot returns the snapshot.func (a EWMASnapshot) Snapshot() EWMA { return a }// Tick panics.func (EWMASnapshot) Tick() {panic("Tick called on an EWMASnapshot")}// Update panics.func (EWMASnapshot) Update(int64) {panic("Update called on an EWMASnapshot")}// NilEWMA is a no-op EWMA.type NilEWMA struct{}// Rate is a no-op.func (NilEWMA) Rate() float64 { return 0.0 }// Snapshot is a no-op.func (NilEWMA) Snapshot() EWMA { return NilEWMA{} }// Tick is a no-op.func (NilEWMA) Tick() {}// Update is a no-op.func (NilEWMA) Update(n int64) {}// StandardEWMA is the standard implementation of an EWMA and tracks the number// of uncounted events and processes them on each tick. It uses the// sync/atomic package to manage uncounted events.type StandardEWMA struct {uncounted int64 // /!\ this should be the first member to ensure 64-bit alignmentalpha float64rate uint64init uint32mutex sync.Mutex}// Rate returns the moving average rate of events per second.func (a *StandardEWMA) Rate() float64 {currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate)) * float64(1e9)return currentRate}// Snapshot returns a read-only copy of the EWMA.func (a *StandardEWMA) Snapshot() EWMA {return EWMASnapshot(a.Rate())}// Tick ticks the clock to update the moving average. It assumes it is called// every five seconds.func (a *StandardEWMA) Tick() {// Optimization to avoid mutex locking in the hot-path.if atomic.LoadUint32(&a.init) == 1 {a.updateRate(a.fetchInstantRate())} else {// Slow-path: this is only needed on the first Tick() and preserves transactional updating// of init and rate in the else block. The first conditional is needed below because// a different thread could have set a.init = 1 between the time of the first atomic load and when// the lock was acquired.a.mutex.Lock()if atomic.LoadUint32(&a.init) == 1 {// The fetchInstantRate() uses atomic loading, which is unecessary in this critical section// but again, this section is only invoked on the first successful Tick() operation.a.updateRate(a.fetchInstantRate())} else {atomic.StoreUint32(&a.init, 1)atomic.StoreUint64(&a.rate, math.Float64bits(a.fetchInstantRate()))}a.mutex.Unlock()}}func (a *StandardEWMA) fetchInstantRate() float64 {count := atomic.LoadInt64(&a.uncounted)atomic.AddInt64(&a.uncounted, -count)instantRate := float64(count) / float64(5e9)return instantRate}func (a *StandardEWMA) updateRate(instantRate float64) {currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate))currentRate += a.alpha * (instantRate - currentRate)atomic.StoreUint64(&a.rate, math.Float64bits(currentRate))}// Update adds n uncounted events.func (a *StandardEWMA) Update(n int64) {atomic.AddInt64(&a.uncounted, n)}
ewma_test.go
package mainimport ("math/rand""sync""testing""time")func BenchmarkEWMA(b *testing.B) {a := NewEWMA1()b.ResetTimer()for i := 0; i < b.N; i++ {a.Update(1)a.Tick()}}func BenchmarkEWMAParallel(b *testing.B) {a := NewEWMA1()b.ResetTimer()b.RunParallel(func(pb *testing.PB) {for pb.Next() {a.Update(1)a.Tick()}})}// exercise race detectorfunc TestEWMAConcurrency(t *testing.T) {rand.Seed(time.Now().Unix())a := NewEWMA1()wg := &sync.WaitGroup{}reps := 100for i := 0; i < reps; i++ {wg.Add(1)go func(ewma EWMA, wg *sync.WaitGroup) {a.Update(rand.Int63())wg.Done()}(a, wg)}wg.Wait()}func TestEWMA1(t *testing.T) {a := NewEWMA1()a.Update(3)a.Tick()if rate := a.Rate(); 0.6 != rate {t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.22072766470286553 != rate {t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.08120116994196772 != rate {t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.029872241020718428 != rate {t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.01098938333324054 != rate {t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.004042768199451294 != rate {t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.0014872513059998212 != rate {t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.0005471291793327122 != rate {t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.00020127757674150815 != rate {t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 7.404588245200814e-05 != rate {t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 2.7239957857491083e-05 != rate {t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 1.0021020474147462e-05 != rate {t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 3.6865274119969525e-06 != rate {t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 1.3561976441886433e-06 != rate {t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 4.989172314621449e-07 != rate {t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 1.8354139230109722e-07 != rate {t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)}}func TestEWMA5(t *testing.T) {a := NewEWMA5()a.Update(3)a.Tick()if rate := a.Rate(); 0.6 != rate {t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.49123845184678905 != rate {t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.4021920276213837 != rate {t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.32928698165641596 != rate {t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.269597378470333 != rate {t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.2207276647028654 != rate {t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.18071652714732128 != rate {t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.14795817836496392 != rate {t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.12113791079679326 != rate {t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.09917933293295193 != rate {t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.08120116994196763 != rate {t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.06648189501740036 != rate {t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.05443077197364752 != rate {t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.04456414692860035 != rate {t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.03648603757513079 != rate {t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.0298722410207183831020718428 != rate {t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)}}func TestEWMA15(t *testing.T) {a := NewEWMA15()a.Update(3)a.Tick()if rate := a.Rate(); 0.6 != rate {t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.5613041910189706 != rate {t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.5251039914257684 != rate {t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.4912384518467888184678905 != rate {t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.459557003018789 != rate {t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.4299187863442732 != rate {t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.4021920276213831 != rate {t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.37625345116383313 != rate {t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.3519877317060185 != rate {t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.3292869816564153165641596 != rate {t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.3080502714195546 != rate {t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.2881831806538789 != rate {t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.26959737847033216 != rate {t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.2522102307052083 != rate {t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.23594443252115815 != rate {t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate)}elapseMinute(a)if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate {t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)}}func elapseMinute(a EWMA) {for i := 0; i < 12; i++ {a.Tick()}}
运行测试命令
PS D:\Projects\Github\NoobWu\go-samples\metrics-demo> go test -v .\ewma_test.go .\ewma.go
=== RUN TestEWMAConcurrency--- PASS: TestEWMAConcurrency (0.00s)=== RUN TestEWMA1--- PASS: TestEWMA1 (0.00s)=== RUN TestEWMA5--- PASS: TestEWMA5 (0.00s)=== RUN TestEWMA15--- PASS: TestEWMA15 (0.00s)PASSok command-line-arguments 0.040s
