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

  1. package main
  2. import (
  3. "math"
  4. "sync"
  5. "sync/atomic"
  6. )
  7. // UseNilMetrics is checked by the constructor functions for all of the
  8. // standard metrics. If it is true, the metric returned is a stub.
  9. //
  10. // This global kill-switch helps quantify the observer effect and makes
  11. // for less cluttered pprof profiles.
  12. var UseNilMetrics bool = false
  13. // EWMAs continuously calculate an exponentially-weighted moving average
  14. // based on an outside source of clock ticks.
  15. type EWMA interface {
  16. Rate() float64
  17. Snapshot() EWMA
  18. Tick()
  19. Update(int64)
  20. }
  21. // NewEWMA constructs a new EWMA with the given alpha.
  22. func NewEWMA(alpha float64) EWMA {
  23. if UseNilMetrics {
  24. return NilEWMA{}
  25. }
  26. return &StandardEWMA{alpha: alpha}
  27. }
  28. // NewEWMA1 constructs a new EWMA for a one-minute moving average.
  29. func NewEWMA1() EWMA {
  30. return NewEWMA(1 - math.Exp(-5.0/60.0/1))
  31. }
  32. // NewEWMA5 constructs a new EWMA for a five-minute moving average.
  33. func NewEWMA5() EWMA {
  34. return NewEWMA(1 - math.Exp(-5.0/60.0/5))
  35. }
  36. // NewEWMA15 constructs a new EWMA for a fifteen-minute moving average.
  37. func NewEWMA15() EWMA {
  38. return NewEWMA(1 - math.Exp(-5.0/60.0/15))
  39. }
  40. // EWMASnapshot is a read-only copy of another EWMA.
  41. type EWMASnapshot float64
  42. // Rate returns the rate of events per second at the time the snapshot was
  43. // taken.
  44. func (a EWMASnapshot) Rate() float64 { return float64(a) }
  45. // Snapshot returns the snapshot.
  46. func (a EWMASnapshot) Snapshot() EWMA { return a }
  47. // Tick panics.
  48. func (EWMASnapshot) Tick() {
  49. panic("Tick called on an EWMASnapshot")
  50. }
  51. // Update panics.
  52. func (EWMASnapshot) Update(int64) {
  53. panic("Update called on an EWMASnapshot")
  54. }
  55. // NilEWMA is a no-op EWMA.
  56. type NilEWMA struct{}
  57. // Rate is a no-op.
  58. func (NilEWMA) Rate() float64 { return 0.0 }
  59. // Snapshot is a no-op.
  60. func (NilEWMA) Snapshot() EWMA { return NilEWMA{} }
  61. // Tick is a no-op.
  62. func (NilEWMA) Tick() {}
  63. // Update is a no-op.
  64. func (NilEWMA) Update(n int64) {}
  65. // StandardEWMA is the standard implementation of an EWMA and tracks the number
  66. // of uncounted events and processes them on each tick. It uses the
  67. // sync/atomic package to manage uncounted events.
  68. type StandardEWMA struct {
  69. uncounted int64 // /!\ this should be the first member to ensure 64-bit alignment
  70. alpha float64
  71. rate uint64
  72. init uint32
  73. mutex sync.Mutex
  74. }
  75. // Rate returns the moving average rate of events per second.
  76. func (a *StandardEWMA) Rate() float64 {
  77. currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate)) * float64(1e9)
  78. return currentRate
  79. }
  80. // Snapshot returns a read-only copy of the EWMA.
  81. func (a *StandardEWMA) Snapshot() EWMA {
  82. return EWMASnapshot(a.Rate())
  83. }
  84. // Tick ticks the clock to update the moving average. It assumes it is called
  85. // every five seconds.
  86. func (a *StandardEWMA) Tick() {
  87. // Optimization to avoid mutex locking in the hot-path.
  88. if atomic.LoadUint32(&a.init) == 1 {
  89. a.updateRate(a.fetchInstantRate())
  90. } else {
  91. // Slow-path: this is only needed on the first Tick() and preserves transactional updating
  92. // of init and rate in the else block. The first conditional is needed below because
  93. // a different thread could have set a.init = 1 between the time of the first atomic load and when
  94. // the lock was acquired.
  95. a.mutex.Lock()
  96. if atomic.LoadUint32(&a.init) == 1 {
  97. // The fetchInstantRate() uses atomic loading, which is unecessary in this critical section
  98. // but again, this section is only invoked on the first successful Tick() operation.
  99. a.updateRate(a.fetchInstantRate())
  100. } else {
  101. atomic.StoreUint32(&a.init, 1)
  102. atomic.StoreUint64(&a.rate, math.Float64bits(a.fetchInstantRate()))
  103. }
  104. a.mutex.Unlock()
  105. }
  106. }
  107. func (a *StandardEWMA) fetchInstantRate() float64 {
  108. count := atomic.LoadInt64(&a.uncounted)
  109. atomic.AddInt64(&a.uncounted, -count)
  110. instantRate := float64(count) / float64(5e9)
  111. return instantRate
  112. }
  113. func (a *StandardEWMA) updateRate(instantRate float64) {
  114. currentRate := math.Float64frombits(atomic.LoadUint64(&a.rate))
  115. currentRate += a.alpha * (instantRate - currentRate)
  116. atomic.StoreUint64(&a.rate, math.Float64bits(currentRate))
  117. }
  118. // Update adds n uncounted events.
  119. func (a *StandardEWMA) Update(n int64) {
  120. atomic.AddInt64(&a.uncounted, n)
  121. }

ewma_test.go

  1. package main
  2. import (
  3. "math/rand"
  4. "sync"
  5. "testing"
  6. "time"
  7. )
  8. func BenchmarkEWMA(b *testing.B) {
  9. a := NewEWMA1()
  10. b.ResetTimer()
  11. for i := 0; i < b.N; i++ {
  12. a.Update(1)
  13. a.Tick()
  14. }
  15. }
  16. func BenchmarkEWMAParallel(b *testing.B) {
  17. a := NewEWMA1()
  18. b.ResetTimer()
  19. b.RunParallel(func(pb *testing.PB) {
  20. for pb.Next() {
  21. a.Update(1)
  22. a.Tick()
  23. }
  24. })
  25. }
  26. // exercise race detector
  27. func TestEWMAConcurrency(t *testing.T) {
  28. rand.Seed(time.Now().Unix())
  29. a := NewEWMA1()
  30. wg := &sync.WaitGroup{}
  31. reps := 100
  32. for i := 0; i < reps; i++ {
  33. wg.Add(1)
  34. go func(ewma EWMA, wg *sync.WaitGroup) {
  35. a.Update(rand.Int63())
  36. wg.Done()
  37. }(a, wg)
  38. }
  39. wg.Wait()
  40. }
  41. func TestEWMA1(t *testing.T) {
  42. a := NewEWMA1()
  43. a.Update(3)
  44. a.Tick()
  45. if rate := a.Rate(); 0.6 != rate {
  46. t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
  47. }
  48. elapseMinute(a)
  49. if rate := a.Rate(); 0.22072766470286553 != rate {
  50. t.Errorf("1 minute a.Rate(): 0.22072766470286553 != %v\n", rate)
  51. }
  52. elapseMinute(a)
  53. if rate := a.Rate(); 0.08120116994196772 != rate {
  54. t.Errorf("2 minute a.Rate(): 0.08120116994196772 != %v\n", rate)
  55. }
  56. elapseMinute(a)
  57. if rate := a.Rate(); 0.029872241020718428 != rate {
  58. t.Errorf("3 minute a.Rate(): 0.029872241020718428 != %v\n", rate)
  59. }
  60. elapseMinute(a)
  61. if rate := a.Rate(); 0.01098938333324054 != rate {
  62. t.Errorf("4 minute a.Rate(): 0.01098938333324054 != %v\n", rate)
  63. }
  64. elapseMinute(a)
  65. if rate := a.Rate(); 0.004042768199451294 != rate {
  66. t.Errorf("5 minute a.Rate(): 0.004042768199451294 != %v\n", rate)
  67. }
  68. elapseMinute(a)
  69. if rate := a.Rate(); 0.0014872513059998212 != rate {
  70. t.Errorf("6 minute a.Rate(): 0.0014872513059998212 != %v\n", rate)
  71. }
  72. elapseMinute(a)
  73. if rate := a.Rate(); 0.0005471291793327122 != rate {
  74. t.Errorf("7 minute a.Rate(): 0.0005471291793327122 != %v\n", rate)
  75. }
  76. elapseMinute(a)
  77. if rate := a.Rate(); 0.00020127757674150815 != rate {
  78. t.Errorf("8 minute a.Rate(): 0.00020127757674150815 != %v\n", rate)
  79. }
  80. elapseMinute(a)
  81. if rate := a.Rate(); 7.404588245200814e-05 != rate {
  82. t.Errorf("9 minute a.Rate(): 7.404588245200814e-05 != %v\n", rate)
  83. }
  84. elapseMinute(a)
  85. if rate := a.Rate(); 2.7239957857491083e-05 != rate {
  86. t.Errorf("10 minute a.Rate(): 2.7239957857491083e-05 != %v\n", rate)
  87. }
  88. elapseMinute(a)
  89. if rate := a.Rate(); 1.0021020474147462e-05 != rate {
  90. t.Errorf("11 minute a.Rate(): 1.0021020474147462e-05 != %v\n", rate)
  91. }
  92. elapseMinute(a)
  93. if rate := a.Rate(); 3.6865274119969525e-06 != rate {
  94. t.Errorf("12 minute a.Rate(): 3.6865274119969525e-06 != %v\n", rate)
  95. }
  96. elapseMinute(a)
  97. if rate := a.Rate(); 1.3561976441886433e-06 != rate {
  98. t.Errorf("13 minute a.Rate(): 1.3561976441886433e-06 != %v\n", rate)
  99. }
  100. elapseMinute(a)
  101. if rate := a.Rate(); 4.989172314621449e-07 != rate {
  102. t.Errorf("14 minute a.Rate(): 4.989172314621449e-07 != %v\n", rate)
  103. }
  104. elapseMinute(a)
  105. if rate := a.Rate(); 1.8354139230109722e-07 != rate {
  106. t.Errorf("15 minute a.Rate(): 1.8354139230109722e-07 != %v\n", rate)
  107. }
  108. }
  109. func TestEWMA5(t *testing.T) {
  110. a := NewEWMA5()
  111. a.Update(3)
  112. a.Tick()
  113. if rate := a.Rate(); 0.6 != rate {
  114. t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
  115. }
  116. elapseMinute(a)
  117. if rate := a.Rate(); 0.49123845184678905 != rate {
  118. t.Errorf("1 minute a.Rate(): 0.49123845184678905 != %v\n", rate)
  119. }
  120. elapseMinute(a)
  121. if rate := a.Rate(); 0.4021920276213837 != rate {
  122. t.Errorf("2 minute a.Rate(): 0.4021920276213837 != %v\n", rate)
  123. }
  124. elapseMinute(a)
  125. if rate := a.Rate(); 0.32928698165641596 != rate {
  126. t.Errorf("3 minute a.Rate(): 0.32928698165641596 != %v\n", rate)
  127. }
  128. elapseMinute(a)
  129. if rate := a.Rate(); 0.269597378470333 != rate {
  130. t.Errorf("4 minute a.Rate(): 0.269597378470333 != %v\n", rate)
  131. }
  132. elapseMinute(a)
  133. if rate := a.Rate(); 0.2207276647028654 != rate {
  134. t.Errorf("5 minute a.Rate(): 0.2207276647028654 != %v\n", rate)
  135. }
  136. elapseMinute(a)
  137. if rate := a.Rate(); 0.18071652714732128 != rate {
  138. t.Errorf("6 minute a.Rate(): 0.18071652714732128 != %v\n", rate)
  139. }
  140. elapseMinute(a)
  141. if rate := a.Rate(); 0.14795817836496392 != rate {
  142. t.Errorf("7 minute a.Rate(): 0.14795817836496392 != %v\n", rate)
  143. }
  144. elapseMinute(a)
  145. if rate := a.Rate(); 0.12113791079679326 != rate {
  146. t.Errorf("8 minute a.Rate(): 0.12113791079679326 != %v\n", rate)
  147. }
  148. elapseMinute(a)
  149. if rate := a.Rate(); 0.09917933293295193 != rate {
  150. t.Errorf("9 minute a.Rate(): 0.09917933293295193 != %v\n", rate)
  151. }
  152. elapseMinute(a)
  153. if rate := a.Rate(); 0.08120116994196763 != rate {
  154. t.Errorf("10 minute a.Rate(): 0.08120116994196763 != %v\n", rate)
  155. }
  156. elapseMinute(a)
  157. if rate := a.Rate(); 0.06648189501740036 != rate {
  158. t.Errorf("11 minute a.Rate(): 0.06648189501740036 != %v\n", rate)
  159. }
  160. elapseMinute(a)
  161. if rate := a.Rate(); 0.05443077197364752 != rate {
  162. t.Errorf("12 minute a.Rate(): 0.05443077197364752 != %v\n", rate)
  163. }
  164. elapseMinute(a)
  165. if rate := a.Rate(); 0.04456414692860035 != rate {
  166. t.Errorf("13 minute a.Rate(): 0.04456414692860035 != %v\n", rate)
  167. }
  168. elapseMinute(a)
  169. if rate := a.Rate(); 0.03648603757513079 != rate {
  170. t.Errorf("14 minute a.Rate(): 0.03648603757513079 != %v\n", rate)
  171. }
  172. elapseMinute(a)
  173. if rate := a.Rate(); 0.0298722410207183831020718428 != rate {
  174. t.Errorf("15 minute a.Rate(): 0.0298722410207183831020718428 != %v\n", rate)
  175. }
  176. }
  177. func TestEWMA15(t *testing.T) {
  178. a := NewEWMA15()
  179. a.Update(3)
  180. a.Tick()
  181. if rate := a.Rate(); 0.6 != rate {
  182. t.Errorf("initial a.Rate(): 0.6 != %v\n", rate)
  183. }
  184. elapseMinute(a)
  185. if rate := a.Rate(); 0.5613041910189706 != rate {
  186. t.Errorf("1 minute a.Rate(): 0.5613041910189706 != %v\n", rate)
  187. }
  188. elapseMinute(a)
  189. if rate := a.Rate(); 0.5251039914257684 != rate {
  190. t.Errorf("2 minute a.Rate(): 0.5251039914257684 != %v\n", rate)
  191. }
  192. elapseMinute(a)
  193. if rate := a.Rate(); 0.4912384518467888184678905 != rate {
  194. t.Errorf("3 minute a.Rate(): 0.4912384518467888184678905 != %v\n", rate)
  195. }
  196. elapseMinute(a)
  197. if rate := a.Rate(); 0.459557003018789 != rate {
  198. t.Errorf("4 minute a.Rate(): 0.459557003018789 != %v\n", rate)
  199. }
  200. elapseMinute(a)
  201. if rate := a.Rate(); 0.4299187863442732 != rate {
  202. t.Errorf("5 minute a.Rate(): 0.4299187863442732 != %v\n", rate)
  203. }
  204. elapseMinute(a)
  205. if rate := a.Rate(); 0.4021920276213831 != rate {
  206. t.Errorf("6 minute a.Rate(): 0.4021920276213831 != %v\n", rate)
  207. }
  208. elapseMinute(a)
  209. if rate := a.Rate(); 0.37625345116383313 != rate {
  210. t.Errorf("7 minute a.Rate(): 0.37625345116383313 != %v\n", rate)
  211. }
  212. elapseMinute(a)
  213. if rate := a.Rate(); 0.3519877317060185 != rate {
  214. t.Errorf("8 minute a.Rate(): 0.3519877317060185 != %v\n", rate)
  215. }
  216. elapseMinute(a)
  217. if rate := a.Rate(); 0.3292869816564153165641596 != rate {
  218. t.Errorf("9 minute a.Rate(): 0.3292869816564153165641596 != %v\n", rate)
  219. }
  220. elapseMinute(a)
  221. if rate := a.Rate(); 0.3080502714195546 != rate {
  222. t.Errorf("10 minute a.Rate(): 0.3080502714195546 != %v\n", rate)
  223. }
  224. elapseMinute(a)
  225. if rate := a.Rate(); 0.2881831806538789 != rate {
  226. t.Errorf("11 minute a.Rate(): 0.2881831806538789 != %v\n", rate)
  227. }
  228. elapseMinute(a)
  229. if rate := a.Rate(); 0.26959737847033216 != rate {
  230. t.Errorf("12 minute a.Rate(): 0.26959737847033216 != %v\n", rate)
  231. }
  232. elapseMinute(a)
  233. if rate := a.Rate(); 0.2522102307052083 != rate {
  234. t.Errorf("13 minute a.Rate(): 0.2522102307052083 != %v\n", rate)
  235. }
  236. elapseMinute(a)
  237. if rate := a.Rate(); 0.23594443252115815 != rate {
  238. t.Errorf("14 minute a.Rate(): 0.23594443252115815 != %v\n", rate)
  239. }
  240. elapseMinute(a)
  241. if rate := a.Rate(); 0.2207276647028646247028654470286553 != rate {
  242. t.Errorf("15 minute a.Rate(): 0.2207276647028646247028654470286553 != %v\n", rate)
  243. }
  244. }
  245. func elapseMinute(a EWMA) {
  246. for i := 0; i < 12; i++ {
  247. a.Tick()
  248. }
  249. }

运行测试命令

  1. PS D:\Projects\Github\NoobWu\go-samples\metrics-demo> go test -v .\ewma_test.go .\ewma.go
  1. === RUN TestEWMAConcurrency
  2. --- PASS: TestEWMAConcurrency (0.00s)
  3. === RUN TestEWMA1
  4. --- PASS: TestEWMA1 (0.00s)
  5. === RUN TestEWMA5
  6. --- PASS: TestEWMA5 (0.00s)
  7. === RUN TestEWMA15
  8. --- PASS: TestEWMA15 (0.00s)
  9. PASS
  10. ok command-line-arguments 0.040s