51Testing软件测试论坛

 找回密码
 (注-册)加入51Testing

QQ登录

只需一步,快速开始

微信登录,快人一步

查看: 1118|回复: 0
打印 上一主题 下一主题

[原创] Golang 单元测试基本使用

[复制链接]
  • TA的每日心情
    无聊
    昨天 09:19
  • 签到天数: 933 天

    连续签到: 5 天

    [LV.10]测试总司令

    跳转到指定楼层
    1#
    发表于 2022-12-29 10:11:44 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
    Go 单测入门篇:Golang 单元测试基本使用
      Golang 单元测试规范
      Go 单元测试概要
      Go 语言的单元测试默认采用官方自带的测试框架,通过引入 testing 包以及 执行 go test 命令来实现单元测试功能。
      在源代码包目录内,所有以 _test.go 为后缀名的源文件会被 go test 认定为单元测试的文件,这些单元测试的文件不会包含在 go build 的源代码构建中,而是单独通过 go test 来编译并执行。
      Go 单元测试的基本规范
      Go 单元测试的基本规范如下:
      每个测试函数都必须导入 testing 包。测试函数的命名类似func TestName(t *testing.T),入参必须是 *testing.T
      测试函数的函数名必须以大写的 Test 开头,后面紧跟的函数名,要么是大写开关,要么就是下划线,比如 func TestName(t *testing.T) 或者  func Test_name(t *testing.T)  都是 ok 的, 但是 func Testname(t *testing.T)不会被检测到
      通常情况下,需要将测试文件和源代码放在同一个包内。一般测试文件的命名,都是 {source_filename}_test.go,比如我们的源代码文件是allen.go ,那么就会在 allen.go 的相同目录下,再建立一个 allen_test.go 的单元测试文件去测试 allen.go 文件里的相关方法。
      当运行 go test 命令时,go test 会遍历所有的 *_test.go 中符合上述命名规则的函数,然后生成一个临时的 main 包用于调用相应的测试函数,然后构建并运行、报告测试结果,最后清理测试中生成的临时文件。
      从一个简单测试用例来确认 go test 的各种使用方法
      一个简单的 xxx_test.go 的单元测试文件如下,里面有两个测试方法:
      package util
      import (
      "testing"
      )
      func TestSum(t *testing.T) {
      if Sum(1, 2, 3) != 6 {
      t.Fatal("sum error")
      }
      }
      func TestAbs(t *testing.T) {
      if Abs(5) != 5 {
      t.Fatal("abs error, except:5, result:", Abs(5))
      }
      }


      go test -v 执行单测并打印详情
      运行方法:进入到包内,运行命令 go test -v ,参数 -v 可以打印详情。
      也可以只运行某个方法的单元测试: go test -v -run="xxx" ,支持正则表达式。
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v
      === RUN   TestSum
      --- PASS: TestSum (0.00s)
      === RUN   TestAbs
      --- PASS: TestAbs (0.00s)
      PASS
      ok  baseCodeExample/gotest0.005s
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v -run="Abs"
      === RUN   TestAbs
      --- PASS: TestAbs (0.00s)
      PASS
      ok  baseCodeExample/gotest0.006s


      go test -v -cover 执行单测并计算覆盖率
      go test 工具还有个功能是测试单元测试的覆盖率,用法为 go test -v -cover, 示例如下:
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -v -cover
      === RUN   TestSum
      --- PASS: TestSum (0.00s)
      === RUN   TestAbs
      --- PASS: TestAbs (0.00s)
      PASS
      coverage: 85.7% of statements
      ok  baseCodeExample/gotest0.005s


      从覆盖率来看(coverage: 85.7% of statements),单元测试没有覆盖全部的代码,只有 85.7% ,我们可以通过如下命令将 cover 的详细信息保存到cover.out 中。
      go test -cover -coverprofile=cover.out -covermode=count
      注:
      -cover 允许代码分析
      -covermode 代码分析模式(set:是否执行;count:执行次数;atomic:次数,并发执行)
      -coverprofile 输出结果文件


      然后再通过
      go tool cover -func=cover.out

      复制代码
      查看每个方法的覆盖率。
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go tool cover -func=cover.out
      baseCodeExample/gotest/compute.go:5:Sum100.0%
      baseCodeExample/gotest/compute.go:13:Abs66.7%
      totalstatements)85.7%


      这里发现是 Abs 方法没有覆盖完全,因为我们的用例只用到了正数的那个分支。
      还可以使用 html 的方式查看具体的覆盖情况。
      go tool cover -html=cover.out

      会默认打开[url=]浏览器[/url],将覆盖情况显示到页面中:

      可以看出 Abs 方法的负数分支没有覆盖到。将 TestAbs 方法修改如下即可:
      func TestAbs(t *testing.T) {
          if Abs(5) != 5 {
              t.Fatal("abs error, except:5, result:", Abs(5))
          }
          if Abs(-4) != 4 {
              t.Fatal("abs error, except:4, result:", Abs(-4))
          }
      }


      再次运行:
      go test -cover -coverprofile=cover2.out -covermode=count
      go tool cover -func=cover2.out


      运行结果如下:
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go test -cover -coverprofile=cover2.out -covermode=count
      PASS
      coverage: 100.0% of statements
      ok  baseCodeExample/gotest0.006s
      allen@MackBook:~/work/goDev/Applications/src/baseCodeExample/gotest$go tool cover -func=cover2.out
      baseCodeExample/gotest/compute.go:5:Sum100.0%
      baseCodeExample/gotest/compute.go:13:Abs100.0%
      totalstatements)100.0%


      这个说明已经达到了 100% 的覆盖率了。
      Go 单测覆盖度的相关命令汇总如下:
      go test -v -cover
      go test -cover -coverprofile=cover.out -covermode=count
      go tool cover -func=cover.out


      Go 单测常见使用方法
      测试单个文件
      通常,一个包里面会有多个方法,多个文件,因此也有多个 test 用例,假如我们只想测试某一个方法的时候,那么我们需要指定某个文件的某个方法。
      如下:
      allen@MackBook:~/work/goDev/Applications/src/gitlab.allen.com/avatar/app_server/service/centralhub$tree .
      .
      ├── msghub.go
      ├── msghub_test.go
      ├── pushhub.go
      ├── rtvhub.go
      ├── rtvhub_test.go
      ├── userhub.go
      └── userhub_test.go
      0 directories, 7 files


      总共有7个文件,其中有三个test文件,如果直接运行 go test,就会测试所有test.go文件了。
      但是,假如我们只更新了 rtvhub.go 里面的代码,所以我只想要测试 rtvhub.go 里面的某个方法,那么就需要指定文件,具体的方法就是同时指定我们需要测试的test.go 文件和 它的源文件,如下:
      go test -v msghub.go  msghub_test.go

      测试单个文件下的单个方法
      在测试单个文件之下,假如我们单个文件下,有多个方法,我们还想只是测试单个文件下的单个方法,要如何实现?我们需要再在此基础上,用 -run 参数指定具体方法或者使用正则表达式。
      假如 test 文件如下:
      package centralhub
      import (
      "context"
      "testing"
      )
      func TestSendTimerInviteToServer(t *testing.T) {
      ctx := context.Background()
      err := sendTimerInviteToServer(ctx, 1461410596, 1561445452, 2)
      if err != nil {
      t.Errorf("send to [url=]server[/url] friendship build failed. %v", err)
      }
      }
      func TestSendTimerInvite(t *testing.T) {
      ctx := context.Background()
      err := sendTimerInvite(ctx, "test", 1461410596, 1561445452)
      if err != nil {
      t.Errorf("send timeinvite to client failed:%v", err)
      }
      }


      只测试 TestSendTimerInvite 方法
      go test -v msghub.go  msghub_test.go -run TestSendTimerInvite
      测试所有正则匹配 SendTimerInvite 的方法
      go test -v msghub.go  msghub_test.go -run "SendTimerInvite"


      测试所有方法
      直接 go test 就行
      竞争检测(race detection)
      go run -race 执行竞争检测
      当两个goroutine并发访问同一个变量,且至少一个goroutine对变量进行写操作时,就会发生数据竞争(data race)。
      为了协助诊断这种bug,Go提供了一个内置的数据竞争检测工具。
      通过传入-race选项,go tool就可以启动竞争检测。
      $ go test -race mypkg    // to test the package
      $ go run -race mysrc.go  // to run the source file
      $ go build -race mycmd   // to build the command
      $ go install -race mypkg // to install the package


      示例代码:
      package main
      import (
      "fmt"
      "time"
      )
      func main() {
      var i int = 0
      go func() {
      for {
      i++
      fmt.Println("subroutine: i = ", i)
      time.Sleep(1 * time.Second)
      }
      }()
      for {
      i++
      fmt.Println("mainroutine: i = ", i)
      time.Sleep(1 * time.Second)
      }
      }


      演示结果:
      $ go run -race testrace.go
      mainroutine: i =  1
      ==================
      WARNING: DATA RACE
      Read at 0x00c0000c2000 by goroutine 6:
        main.main.func1()
            /Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:12 +0x3c
      Previous write at 0x00c0000c2000 by main goroutine:
        main.main()
            /Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:18 +0x9e
      Goroutine 6 (running) created at:
        main.main()
            /Users/wudebao/Documents/workspace/goDev/Applications/src/base-code-example/system/testrace/testrace.go:10 +0x7a
      ==================
      subroutine: i =  2
      mainroutine: i =  3
      subroutine: i =  4
      mainroutine: i =  5
      subroutine: i =  6
      mainroutine: i =  7
      subroutine: i =  8
      subroutine: i =  9
      mainroutine: i =  10



    分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友
    收藏收藏
    回复

    使用道具 举报

    本版积分规则

    关闭

    站长推荐上一条 /1 下一条

    小黑屋|手机版|Archiver|51Testing软件测试网 ( 沪ICP备05003035号 关于我们

    GMT+8, 2024-4-20 13:41 , Processed in 0.064046 second(s), 23 queries .

    Powered by Discuz! X3.2

    © 2001-2024 Comsenz Inc.

    快速回复 返回顶部 返回列表