开发者

Go 语言进阶单元测试示例详解

目录
  • 前言
  • 测试
  • 单元测试
    • 规则
    • 示例
    • assert
    • 覆盖率
    • 依赖
  • Mock
    • 基准测试

      前言

      本文从单元测试实践角度出发,提升对代码质量的意识。

      本文内容主要包括:单元测试、Mock测试、基准测试。

      测试

      测试可以提高代码的质量、减少事故的发生。

      测试又分为:回归测试、集成测试、单元测试。

      回归测试是指对QA手动回归一些特定场景,可以理解为我们说的手动点点。

      集成测试是指对系统功能维度做验证,比如对服务暴露的接口验证,一般是自动化的验证。

      单元测试是指在开发阶段,开发者对单独的函数、模块做验证,写一些测试用例。

      Go 语言进阶单元测试示例详解

      单元测试

      单元测试组成部分:输入、输出、测试单元、与期望的校对,测试单元又包括函数、接bxoEGOMB口、模块、复杂的聚合函数等。

      通过单元测试的输出再与期望输出进行校对,来验证代码的正确性。通过单元测试可以保证代码的质量,也可以在一定程度上提升效率,比如通过运行单元测试可以快速定位到有问题的代码。

      Go 语言进阶单元测试示例详解

      规则

      单元测试的编写有一定的规则:

      • 所有测试文件以 _test.go 结尾
      • 测试方法名以Test开头,参数要用testin编程客栈g func TestXxx(t *testing.T)
      • 测试初始化逻辑放到TestMain
      • 通过go test命令进行测试

      示例

      import "testing"
      func TestHelloTom(t *testing.T) {
          output := HelloTom()
          expectOutPut := "Tom"
          if output != expectOutPut {
              t.Errorf("Expected %s do not match actual %s", expectOutPut, output)
          }
      }
      func HelloTom() string {
          return "Jerry"
      }
      

      通过go test命令运行得到以下结果,从测试结果里可以看出,我们期望得到的是Tom,但实际得到的却是Jerry

      --- FAIL: TestHelloTom (0.00s)
      helloTom_test.go:9: Expected Tom do not match actual Jerry
      FAIL
      exit status 1
      FAIL    learning/mytesting      0.496s
      

      assert

      另外我们可以使用开源的assert包,来代替我们自己的if判断。

      func TestHelloTom(t *testing.T) {
          output := HelloTom()
          expectOutPut := "Tom"
          assert.Equal(t, expectOutPut, output)
      }
      

      再次通过go test命令运行得到以下结果,输出了更详细的堆栈信息。

      Go 语言进阶单元测试示例详解

      覆盖率

      如何评估单元测试呢?是通过代码覆盖率来评估的,评估的标准包括:

      • 衡量代码是否经过了足够的测试
      • 评价项目的测试水准
      • 评估项目是否达到了高水平的测试等级

      下面通过一个例子来看下代码覆盖率:

      // judgepass.go
      func JudgePassLine(score int16) bool {
          if score >= 16 {
              return true
          }
          return false
      }
      // judgepass_test.go
      func TestJudgePassLineTrue(t *testing.开发者_Python开发T) {
          isPass := JudgePassLine(70)
          assert.Equal(t, true, isPass)
      }
      

      通过命令来看覆盖率go test judgepass_test.go judgepass.go --cover

      输出结果为:

      ok  command-line-arguments  0.327s  coverage: 66.7% of statements

      可以看到,提示出的代码覆盖率为66.7%,因为只走了一个if分支。如果要想达到100%的代码覆盖率的话,就要把所有的分支都要覆盖到。

      一般的覆盖率为50%~60%,较高的覆盖率要达到80%+。要注意:测试分支相互独立、要全面覆盖,测试单元粒度要足够小,满足函数单一职责。

      依赖

      一个实际项目不可能只是一个简单的单体函数,肯定会很复杂,存在其他的依赖,比如依赖数据库、Redis、文件等外部依赖。

      单元测试一般有两个目标:幂等、稳定。

      幂等:重复执行一个用例、调用一个接口,返回的结果是一样的。

      稳定:单元测试是相互隔离的,在任何时间都能独立运行。

      Go 语言进阶单元测试示例详解

      Mock

      如果单元测试用到数据库、redis等,在单元测试里直接连接会涉及到网络传输,这是不稳定的,所以要用到Mock机制。

      开源Mock框架:github.com/bouk/monkey

      这个Mock包可以对函数或方法进行打桩,打桩就是用一个函数A来替换一个函数B。

      monkey的实现原理主要是在运行时,通过Gounsafe包能够将内存中函数的地址替换为运行时函数的地址,最终调用的是打桩函数,从而实现Mock的功能。

      Mock常用方法:PatchUnpatch

      Patch方法有两个参数,target为替换的函数(原函数),replacement为要替换成的函数。

      func Patch(target, replacement interface{}) *PatchGuard {
          t := reflect.ValueOf(target)
          r := reflect.ValueOf(replacement)
          patchValue(t, r)
          return &PatchGuard{t, r}
      }
      

      Unpatch为测试结束之后,要把打的桩给卸载掉。

      func Unpatch(target interface{}) bool {
          return unpatchValue(reflect.ValueOf(target))
      }
      

      下面通过Mock来模拟对文件的操作。

      func TestProcessFirstLineWithMock(t *testing.T) {
          monkey.Patch(ReadFirstLine, func() string {
              return "line110"
          })
          defer monkey.Unpatch(ReadFirstLine)
          line := ProcessFirstLine()
          assert.Equal(t, "line000", line)
      }
      func ReadFirstLine() string {
          open, err := os.Open("log")
          defer open.Close()
          if err != nil {
              return ""
          }
          scanner := bufio.NewScanner(open)
          for scanner.Scan() {
              return scanner.Text()
          }
          return ""
      }
      func ProcessFirstLine() string {
          line := ReadFirstLine()
          destLine := strings.ReplaceAll(line, "11", "00")
          return destLine
      }
      

      该测试用例对ProcessFirstLine函数进行测试,这个函数调用了ReadFirstLine函数,涉及到文件的操作,通过Mock对文件的操作进行打桩,这样就避免了其他进程对文件操作的影响。

      基准测试

      Go还提供了基准测试框架,可以测试一段程序的性能、CPU消耗,可以对代码做性能分析,测试方法与单元测试类似。

      基准测试php规则:

      • 基准测试以Benchmark为前缀
      • 需要一个*testing.B类型的参数b
      • 基准测试必须要执行b.N次

      下面通过一个模拟负载均衡的例子,来看下基准测试:

      var ServerIndex [10]int
      func InitServerIndex() {
          for i := 0; i < 10; i++ {
              ServerIndex[i] = i + 100
          }
      }
      func Select() int {
          return ServerIndex[rand.Intn(10)]
      }
      func BenchmarkSelect(b *testing.B) {
          InitServerIndex()
          b.ResetTimer()
          for i := 0; i < b.N; i++ {
              Select()
          }
      }
      func BenchmarkSelectParallel(b *testing.B)  {
          InitServerIndex()
          b.ResetTimer()
          b.Ru编程客栈nParallel(func (pb *testiwww.devze.comng.PB)  {
              for pb.Next() {
                  Select()
              }
          })
      }
      

      通过命令 go test -bench=. 运行测试,输出结果如下:

      goos: darwin

      goarch: amd64

      pkg: learning/bench

      cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.70GHz

      BenchmarkSelect-8               50264580               23.47 ns/op

      BenchmarkSelectParallel-8       13717840               133.4 ns/op

      PASS

      ok      learning/bench  4.559s

      BenchmarkSelect-8 表示对Select函数进行基准测试,数字8表示 GOMAXPROCS 的值。

      23.47 ns/op 表示每次调用Select函数耗时23.47ns

      50264580 这是50264580次调用的平均值。

      字节开源的go框架:github.com/bytedance/g…

      引用 Go 语言进阶与依赖管理

      以上就是Go 语言进阶单元测试示例详解的详细内容,更多关于Go 语言单元测试的资料请关注我们其它相关文章!

      0

      上一篇:

      下一篇:

      精彩评论

      暂无评论...
      验证码 换一张
      取 消

      最新开发

      开发排行榜