1. 简述

Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试。

go test [-c] [-i] [build flags] [packages] [flags for test binary]

参数解读

-c : 编译go test成为可执行的二进制文件,但是不运行测试。

-i : 安装测试包依赖的package,但是不运行测试。

关于build flags,调用go help build,这些是编译运行过程中需要使用到的参数,一般设置为空

关于packages,调用go help packages,这些是关于包的管理,一般设置为空

关于flags for test binary,调用go help testflag,这些是go test过程中经常使用到的参数

-test.v : 是否输出全部的单元测试用例(不管成功或者失败),默认没有加上,所以只输出失败的单元测试用例。

-test.run pattern: 只跑哪些单元测试用例

-test.bench patten: 只跑哪些性能测试用例

-test.benchmem : 是否在性能测试的时候输出内存情况

-test.benchtime t : 性能测试运行的时间,默认是1s

-test.cpuprofile cpu.out : 是否输出cpu性能分析文件

-test.memprofile mem.out : 是否输出内存性能分析文件

-test.blockprofile block.out : 是否输出内部goroutine阻塞的性能分析文件

-test.memprofilerate n : 内存性能分析的时候有一个分配了多少的时候才打点记录的问题。这个参数就是设置打点的内存分配间隔,也就是profile中一个sample代表的内存大小。默认是设置为512 * 1024的。如果你将它设置为1,则每分配一个内存块就会在profile中有个打点,那么生成的profile的sample就会非常多。如果你设置为0,那就是不做打点了。

你可以通过设置memprofilerate=1和GOGC=off来关闭内存回收,并且对每个内存块的分配进行观察。

-test.blockprofilerate n: 基本同上,控制的是goroutine阻塞时候打点的纳秒数。默认不设置就相当于-test.blockprofilerate=1,每一纳秒都打点记录一下

-test.parallel n : 性能测试的程序并行cpu数,默认等于GOMAXPROCS。

-test.timeout t : 如果测试用例运行时间超过t,则抛出panic

-test.cpu 1,2,4 : 程序运行在哪些CPU上面,使用二进制的1所在位代表,和nginx的nginx_worker_cpu_affinity是一个道理

-test.short : 将那些运行时间较长的测试用例运行时间缩短

-test.count:设置执行测试函数的次数,默认为1

注:上述选项中“test.”可以省略。

2. 单元测试

go测试文件命令为_test.go,测试函数定义:func TestXXX(t *testing.T)。test框架运行每个测试函数。假如测试失败,通过t.Error或t.Fail返回错误。

go test命令只能在一个目录下执行所有文件,所以代码和测试代码应在同一目录下。

测试命令

go test //运行当前目录下所有测试文件

go test -v //显示详细测试信息

go test -v -cover //显示测试覆盖情况,即测试函数占全部函数的百分比,可不测试所有函数

测试文件规则

  • 文件名必须是_test.go结尾的

  • 你必须import testing这个包

  • 所有的测试用例函数必须是Test开头

  • 测试用例会按照源代码中写的顺序依次执行

  • 测试函数TestXxx()的参数是testing.T,我们可以使用该类型来记录错误或者是测试状态

  • 测试格式:func TestXxx (t *testing.T),Xxx部分可以为任意的字母数字的组合,但是首字母不能是小写字母[a-z],例如Testintdiv是错误的函数名。

  • 函数中通过调用testing.T的Error, Errorf, FailNow, Fatal, FatalIf方法,说明测试不通过,调用Log方法用来记录测试的信息。

// reverse.go
package stringutil func Reverse(s string) string {
r := []rune(s)
for i,j := , len(r)-; i< len(r)/; i,j= i+, j- {
r[i], r[j] = r[j], r[i]
}
return string(r)
} func PrefixString(s, pre string) string {
return pre+s
} // reverse_test.go
package stringutil
import (
"testing"
"strconv"
) func TestReverse(t *testing.T) {
cases := []struct {
in, want string
}{
{"Hello, world", "dlrow ,olleH"},
{"Hello, 世界", "界世 ,olleH"},
{"", ""},
}
for _, c := range cases {
got := Reverse(c.in)
if got != c.want {
t.Errorf("Reverse(%q) == %q, wang %q", c.in, got, c.want)
}
}
} func TestReverse2(t *testing.T){
s1 := ""
s2 := "" sr := Reverse(s1)
if sr != s2 {
t.Error("测试不通过")
} else {
t.Log("测试通过")
}
} func BenchmarkReverse1(b *testing.B){
for i := ; i < b.N; i++{
Reverse(PrefixString(strconv.Itoa(+i), ""))
}
} func BenchmarkTimeConsume(b *testing.B){
b.StopTimer()
b.Log("Starting test Benchmark...\n")
b.StartTimer()
for i := ; i < b.N; i++{
Reverse(PrefixString(strconv.Itoa(+i), ""))
}
}

运行结果:

$ go test -v -cover
=== RUN TestReverse
--- PASS: TestReverse (.00s)
=== RUN TestReverse2
--- PASS: TestReverse2 (.00s)
reverse_test.go:: 测试通过
PASS
coverage: 80.0% of statements
ok github.com/yuxi-o/golang/stringutil .003s

3. 压力测试

压力测试用来检测函数(方法)的性能。

  • 压力测试用例必须遵循如下格式,其中XXX可以是任意字母数字的组合,但是首字母不能是小写字母。func BenchmarkXXX(b *testing.B) { ... }

  • go test不会默认执行压力测试的函数,如果要执行压力测试需要带上参数-test.bench,语法:-test.bench="test_name_regex",例如go test -test.bench=".*"表示测试全部的压力测试函数

  • 在压力测试用例中,请记得在循环体内使用testing.B.N,以使测试可以正常的运行

  • 文件名也必须以_test.go结尾,可与单元测试在同一个文件

  • -benchmem可附加内存信息,观察内存分配情况。
$ go test -bench=. -count=
goos: linux
goarch: amd64
pkg: github.com/yuxi-o/golang/stringutil
BenchmarkReverse1- ns/op
BenchmarkReverse1- ns/op
BenchmarkTimeConsume- ns/op
--- BENCH: BenchmarkTimeConsume-
reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... ... [output truncated]
BenchmarkTimeConsume- ns/op
--- BENCH: BenchmarkTimeConsume-
reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... reverse_test.go:: Starting test Benchmark... ... [output truncated]
PASS
ok github.com/yuxi-o/golang/stringutil .805s

4. BDD

行为驱动开发(Behavior-Driven Development)

行为驱动开发的根基是一种“通用语言”。这种通用语言同时被客户和开发者用来定义系统的行为。由于客户和开发者使用同一种“语言”来描述同一个系统,可以最大程度避免表达不一致带来的问题。

用业务领域的语言来描述:

Given: a user is creating an account

When: they specify an insecure password

Then: they see a message, "Passwords must be at least 8 characters long with at least one letter, one number, and one symbol"

goconvey框架实现了一个BDD: https://github.com/smartystreets/goconvey

go get github.com/smartystreets/goconvey

启动web ui
$GOPATH/bin/goconvey

 

5. 高级测试技术

  1. 一个例子程序

outyet是一个web服务,用于宣告某个特定Go版本是否已经打标签发布了。其获取方法:

go get github.com/golang/example/outyet

注:go get执行后,cd $GOPATH/src/github.com/golang/example/outyet下,执行go run main.go。然后用浏览器打开http://localhost:8080即可访问该Web服务了。

  1. 测试Http客户端和服务端

net/http/httptest包提供了许多帮助函数,用于测试那些发送或处理Http请求的代码。

  1. httptest.Server

httptest.Server在本地回环网口的一个系统选择的端口上listen。它常用于端到端的HTTP测试。

type Server struct {
    URL      string // base URL of form http://ipaddr:port with no trailing slash
    Listener net.Listener
    // TLS is the optional TLS configuration, populated with a new config
    // after TLS is started. If set on an unstarted server before StartTLS
    // is called, existing fields are copied into the new config.
    TLS *tls.Config
    // Config may be changed after calling NewUnstartedServer and
    // before Start or StartTLS.
    Config *http.Server
}
func NewServer(handler http.Handler) *Server
func (*Server) Close() error
  1. httptest.Server实战

下面代码创建了一个临时Http Server,返回简单的Hello应答:

 ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintln(w, "Hello, client")
    }))
    defer ts.Close()
    res, err := http.Get(ts.URL)
    if err != nil {
        log.Fatal(err)
    }
    greeting, err := ioutil.ReadAll(res.Body)
    res.Body.Close()
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("%s", greeting)
  1. httptest.ResponseRecorder

httptest.ResponseRecorder是http.ResponseWriter的一个实现,用来记录变化,用在测试的后续检视中。

type ResponseRecorder struct {
    Code      int           // the HTTP response code from WriteHeader
    HeaderMap http.Header   // the HTTP response headers
    Body      *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
    Flushed   bool
}
  1. httptest.ResponseRecorder实战

向一个HTTP handler中传入一个ResponseRecorder,通过它我们可以来检视生成的应答。

    handler := func(w http.ResponseWriter, r *http.Request) {
        http.Error(w, "something failed", http.StatusInternalServerError)
    }
    req, err := http.NewRequest("GET", "http://example.com/foo", nil)
    if err != nil {
        log.Fatal(err)
    }
    w := httptest.NewRecorder()
    handler(w, req)
    fmt.Printf("%d – %s", w.Code, w.Body.String())
  1. 竞争检测(race detection)

当两个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

注:一个数据竞争检测的例子

例子代码:

//testrace.go
package main
import "fmt"
import "time"
func main() {
        var i int =
        go func() {
                for {
                        i++
                        fmt.Println("subroutine: i = ", i)
                        time.Sleep( * time.Second)
                }
        }()
        for {
                i++
                fmt.Println("mainroutine: i = ", i)
                time.Sleep( * time.Second)
        }
}
$go run -race testrace.go
mainroutine: i = 
==================
WARNING: DATA RACE
Read by goroutine :
  main.func·()
      /Users/tony/Test/Go/testrace.go: +×
Previous write by main goroutine:
  main.main()
      /Users/tony/Test/Go/testrace.go: +0xd5
Goroutine (running) created at:
  main.main()
      /Users/tony/Test/Go/testrace.go: +0xaf
==================
subroutine: i = 
mainroutine: i = 
subroutine: i = 
mainroutine: i = 
subroutine: i = 
mainroutine: i = 
subroutine: i = 
  1. 测试并发(testing with concurrency)

当测试并发代码时,总会有一种使用sleep的冲动。大多时间里,使用sleep既简单又有效。

但大多数时间不是”总是“。

我们可以使用Go的并发原语让那些奇怪不靠谱的sleep驱动的测试更加值得信赖。

  1. 使用静态分析工具vet查找错误

vet工具用于检测代码中程序员犯的常见错误:

– 错误的printf格式

– 错误的构建tag

– 在闭包中使用错误的range循环变量

– 无用的赋值操作

– 无法到达的代码

– 错误使用mutex

等等。

使用方法:

go vet [package]

  1. 从内部测试

golang中大多数测试代码都是被测试包的源码的一部分。这意味着测试代码可以访问包种未导出的符号以及内部逻辑。就像我们之前看到的那样。

注:比如$GOROOT/src/pkg/path/path_test.go与path.go都在path这个包下。

  1. 从外部测试

有些时候,你需要从被测包的外部对被测包进行测试,比如测试代码在package foo_test下,而不是在package foo下。

这样可以打破依赖循环,比如:

– testing包使用fmt

– fmt包的测试代码还必须导入testing包

– 于是,fmt包的测试代码放在fmt_test包下,这样既可以导入testing包,也可以同时导入fmt包。

  1. Mocksfakes

通过在代码中使用interface,Go可以避免使用mock和fake测试机制。

例如,如果你正在编写一个文件格式解析器,不要这样设计函数:

func Parser(f *os.File) error

作为替代,你可以编写一个接受interface类型的函数:

func Parser(r io.Reader) error

和bytes.Buffer、strings.Reader一样,*os.File也实现了io.Reader接口。

  1. 子进程测试

有些时候,你需要测试的是一个进程的行为,而不仅仅是一个函数。例如:

func Crasher() {
    fmt.Println("Going down in flames!")
    os.Exit()
}

为了测试上面的代码,我们将测试程序本身作为一个子进程进行测试:

func TestCrasher(t *testing.T) {
    if os.Getenv("BE_CRASHER") == "" {
        Crasher()
        return
    }
    cmd := exec.Command(os.Args[], "-test.run=TestCrasher")
    cmd.Env = append(os.Environ(), "BE_CRASHER=1")
    err := cmd.Run()
    if e, ok := err.(*exec.ExitError); ok && !e.Success() {
        return
    }
    t.Fatalf("process ran with err %v, want exit status 1", err)
}

参考:

  1. https://studygolang.com/articles/2801 Golang测试技术

  2. https://studygolang.com/articles/11954 golang测试

  3. https://studygolang.com/articles/16801 go mock测试 模仿测试

  4. https://studygolang.com/articles/9391 go语言单元测试

  5. https://studygolang.com/articles/21445 go mock测试 httptest应用

  6. Go语言从入门到实战 蔡超 极客时间

golang测试的更多相关文章

  1. Golang测试包

    Golang测试包 golang自带了测试包(testing),直接可以进行单元测试.性能分析.输出结果验证等.简单看着官方文档试了试,总结一下: 目录结构和命令 使用golang的测试包,需要遵循简 ...

  2. Golang测试技术

    本篇文章内容来源于Golang核心开发组成员Andrew Gerrand在Google I/O 2014的一次主题分享“Testing Techniques”,即介绍使用Golang开发 时会使用到的 ...

  3. golang测试框架--smartystreets/goconvey

    视频教程和配套博客:goconvey - 课时 1:优雅的单元测试 Go 语言虽然自带单元测试功能,在 GoConvey 诞生之前也出现了许多第三方辅助库.但没有一个辅助库能够像 GoConvey 这 ...

  4. Golang | 测试与性能调优

    Test 我们在日常的工作过程中,自测是不可缺少的,公司还会要求所有的公共方法必须要写单测,在别的语言中,我们如果想要写单测还需要使用到测试框架,但是Go语言中,直接支持测试,并且使用起来非常简单. ...

  5. golang测试与性能调优

  6. golang 项目实战简明指南

    原文地址 开发环境搭建 golang 的开发环境搭建比较简单,由于是编译型语言,写好 golang 源码后,只需要执行 go build 就能将源码编译成对应平台(本文中默认为 linux)上的可执行 ...

  7. Go测试,功能测试,性能测试,测试辅助,go test 工具,高级测试,IO相关测试,黑盒测试,HTTP测试,进程测试

    go命令教程: http://wiki.jikexueyuan.com/project/go-command-tutorial/0.5.html Go测试 第一个测试 “Hello Test!” 首先 ...

  8. Golang的演化历程

    本文来自Google的Golang语言设计者之一Rob Pike大神在GopherCon2014大会上的开幕主题演讲资料“Hello, Gophers!”.Rob大神在这次分 享中用了两个生动的例子讲 ...

  9. go基础语法-常量与枚举

    1.常量定义 用const关键字修饰常量名并赋值,常量命名不同于java等语言,golang中一般用小写,因为在golang中首字母大写表示public权限 const a = 3 2.常量使用 使用 ...

随机推荐

  1. [FJOI2018]所罗门的宝藏

    大概是最后一篇题解,其实只是想颓废一下打个故事 据古代传说记载,所罗门王即是智慧的代表,又是财富的象征.他建立了强大而富有的国家,聚集了大批的黄金象牙和钻石,并把这些价值连城的珍宝藏在一个神秘的地方, ...

  2. Python中的next()\iter()函数详解

    什么是可迭代的对象(Iterable,即可以用for循环的对象)和迭代器(Iterator) Iterable: 一类是:list.tuple.dict.set.str 二类是:generator(都 ...

  3. 趣谈Linux操作系统学习笔记:第二十七讲

    一.文件系统的功能规划 1.引子 咱们花了这么长的时间,规划了会议室管理系统,这样多个项目执行的时候,隔离性可以得到保证. 但是,会议室里面被回收,会议室里面的资料就丢失了.有一些资料我们希望项目结束 ...

  4. 第02组 团队Git现场编程实战

    目录 1. 组员职责分工(2分) 2. github 的提交日志截图(1分) 3. 程序运行截图(3分) 4. 程序运行环境(1分) 5. GUI界面(5分) 6. 基础功能实现(10分) 7. 鼓励 ...

  5. java连接redis中的数据查、增、改、删操作的方法

    package com.lml.redis; import java.util.HashMap;import java.util.Iterator;import java.util.Map;impor ...

  6. QFileInfo().created() 警告 created is deprecated 怎么改?

    有这样一行代码操作: QFileInfo(...).created().toString(...); QtCreator提示警告: 'created' is deprecated 'created' ...

  7. 设计高性能大并发WEB系统架构注意点

    设计高性能大并发WEB系统架构注意点 第01:大型架构的演进之路第02(上):分布式缓存第02(下):分布式缓存第03:分布式消息队列第04:分布式数据存储第05:分布式服务框架第06:高性能系统架构 ...

  8. Hibernate的Hql语句使用in关键字

    原文地址:https://blog.csdn.net/u013410747/article/details/50954867

  9. Python内网渗透扫描器Ladon

    Ladon Scanner For Python PyLadon 目前python版功能较少,无论在Windows还是Linux系统性能以及速度均也比不上Ladon.exe 唯一的优点是跨平台,后续会 ...

  10. C# 读取配置指定Config文件--亲测通过

    直接上代码: public class ConfigUtils { public static String GetKey(String configPath,String key) { Config ...