# 测试

Go自带一个轻量级的测试框架`testing`，以及`go test`命令，用于实现**单元测试**和**性能测试**。`testing`与其他语言的测试框架类似，可以针对响应函数编写测试用例，也可以基于框架写相应的压力测试用例。可以从如下几个方面保证代码质量：

1. 确保每个函数是可运行的，并且运行结果是正确的
2. 确保写出的代码性能，`testing`会展示每个测试用例执行的时间
3. 及时发现程序设计的逻辑错误，让问题尽早暴露

## 使用`testing`测试

### 使用案例

目标测试包结构，该包主要提供加减乘除四个方法：

```
cal
├── cal.go
├── cal_test.go
└── sub_test.go
```

`cal.go`：

```go
package cal

func Plus(a, b int) int {
    return a + b
}

func Sub(a, b int) int {
    return a - b
}

func Multi(a, b int) int {
    return a * b
}

func Division(a, b int) int  {
    return a / b
}
```

`cal_test.go（`针对加乘除三个函数测试）：

```go
package cal

import (
    "testing"
)

func TestPlus(t *testing.T) {
    res := Plus(1, 2)
    e := 3 // 期望值

    if res != 3 { // 失败
        t.Fatalf("执行失败：期望值 %v， 目标值 %v ", e, res) // 发生致命错误，并格式化错误信息
    }
    t.Logf("执行成功：期望值 %v， 目标值 %v", e, res) // 打印日志
}

func TestDivision(t *testing.T) {
    res := Division(1, 2)
    t.Logf("执行成功：目标值 %v", res)
}

// 测试除零的情况
func TestDivisionByZero(t *testing.T) {
    res := Division(1, 0)
    t.Logf("执行成功：目标值 %v", res) // 打印日志
}
```

`sub_test.go`（针对减法函数测试）：

```go
package cal

import "testing"

func TestSub(t *testing.T) {
    res := Sub(1, 2)
    if res != -1 {
        t.Fatalf("执行失败：期望值 %v， 目标值 %v ", -1, res) // 失败并打印信息
    }
}
```

**执行命令`go test -v`可以测试当前所在文件夹的包中的所有测试用例：**

```
$ go test -v 
=== RUN   TestPlus  # Plus方法通过
    cal_test.go:14: 执行成功：期望值 3， 目标值 3
--- PASS: TestPlus (0.00s)
=== RUN   TestDivision # Division方法通过
    cal_test.go:19: 执行成功：目标值 0 
--- PASS: TestDivision (0.00s) # 这是运行时间
=== RUN   TestDivisionByZero # Division除0不通过
--- FAIL: TestDivisionByZero (0.00s)
panic: runtime error: integer divide by zero [recovered]
        panic: runtime error: integer divide by zero

goroutine 8 [running]:
testing.tRunner.func1.2(0x11217e0, 0x1215b40)
        /usr/local/go/src/testing/testing.go:1143 +0x332
testing.tRunner.func1(0xc000001800)
        /usr/local/go/src/testing/testing.go:1146 +0x4b6
panic(0x11217e0, 0x1215b40)
        /usr/local/go/src/runtime/panic.go:965 +0x1b9
cal.Division(...)
        /Users/yangsx/GoLandProjects/notes-golang/src/cal/cal.go:16
cal.TestDivisionByZero(0xc000001800)
        /Users/yangsx/GoLandProjects/notes-golang/src/cal/cal_test.go:23 +0x12
testing.tRunner(0xc000001800, 0x114d280)
        /usr/local/go/src/testing/testing.go:1193 +0xef
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1238 +0x2b3
exit status 2
FAIL    cal     0.353s
```

### 只打印测试失败结果

去除`-v`参数即可，如下测试就没有打印任何成功的测试用例：

```
$ go test    
--- FAIL: TestDivisionByZero (0.00s)
panic: runtime error: integer divide by zero [recovered]
        panic: runtime error: integer divide by zero

goroutine 18 [running]:
testing.tRunner.func1.2(0x11217e0, 0x1215b40)
        /usr/local/go/src/testing/testing.go:1143 +0x332
testing.tRunner.func1(0xc000082600)
        /usr/local/go/src/testing/testing.go:1146 +0x4b6
panic(0x11217e0, 0x1215b40)
        /usr/local/go/src/runtime/panic.go:965 +0x1b9
cal.Division(...)
        /Users/yangsx/GoLandProjects/notes-golang/src/cal/cal.go:16
cal.TestDivisionByZero(0xc000082600)
        /Users/yangsx/GoLandProjects/notes-golang/src/cal/cal_test.go:23 +0x12
testing.tRunner(0xc000082600, 0x114d280)
        /usr/local/go/src/testing/testing.go:1193 +0xef
created by testing.(*T).Run
        /usr/local/go/src/testing/testing.go:1238 +0x2b3
exit status 2
FAIL    cal     0.726s
```

### 针对某个测试文件测试

注意，一定要带上被测试的原文件：

```
$ go test sub_test.go  cal.go 
ok      command-line-arguments  0.421s
```

### 针对某个测试函数测试

仅测试`TestSub`单元测试函数：

```
$ go test -v -test.run TestSub
=== RUN   TestSub
--- PASS: TestSub (0.00s)
PASS
ok      cal     0.235s
```

### 使用注意事项

1. 测试文件必须以`_test.go`结尾，前缀则不要求与源码文件相同
2. 测试函数必须以`Test`开头，后缀通常是被测函数的名称（不是必需的），并且首字母必须大写，否则不会识别。比如`TestDivision`，`TestDivisionByZero`
3. 测试函数的参数必须为`t testing.T`
4. 测试函数体中需要打印日志的情况可以使用`t.Logf`方法打印
5. 如果出现错误，也就是不符合期望值，可以调用`t.Error()`,`t.Fatalf()`等方法标记当前测试失败
6. 如果测试过程中发生异常，测试用例失败

### `testing`的原理

1. `go test`工具将测试文件`cal_test.go`引入到自己的`main`中
2. 在`main`中调用所有的测试用例

类似如下伪代码：

```go
func main() {
    TestXXX()
    ...
}
```

## goconvey

<https://github.com/smartystreets/goconvey>

特点：

1. 测试代码优雅简洁
2. 直接集成Go原生测试
3. 全自动编译测试
4. 详细展示测试结果以及覆盖率
5. 高可读性的命令行输出结果
6. 半自动化书写测试用例

安装：

```shell
$ go get github.com/smartystreets/goconvey

# 或者

$ go install github.com/smartystreets/goconvey 
```

### 使用案例

目标测试文件 `calc.go`：

```go
package goconvey

import "errors"

func Add(a, b int) int {
	return a + b
}

func Subtract(a, b int) int {
	return a - b
}

func Multiply(a, b int) int {
	return a * b
}

func Division(a, b int) (int, error) {
	if b == 0 {
		return 0, errors.New("除数不能为0")
	}
	return a / b, nil
}
```

创建测试文件 `calc_test.go`：

```go
package goconvey

import (
	. "github.com/smartystreets/goconvey/convey" // 导入convey包，使用Convey函数以及So等函数
	"testing"                                    // go testing 测试框架支持
)

func TestAdd(t *testing.T) {
	// 使用Convey方法声明一个测试的范围，主要包含 描述、以及一个用于执行测试逻辑的func()
	Convey("将两个数相加", t, func() {
		// 用于断言的通用方法，他可以接收如下三个参数
		// 1, 实际值
		// 2, 判断方式，这里是相同
		// 3, 期望值
		// 如果实际值不等于期望值，则代表测试未通过
		So(Add(1, 2), ShouldEqual, 3)
	})
}

func TestSubtract(t *testing.T) {
	Convey("将两个数相减", t, func() {
		So(Subtract(1, 2), ShouldEqual, -1)
	})
}

func TestMultiply(t *testing.T) {
	Convey("将两个数相乘", t, func() {
		So(Multiply(1, 2), ShouldEqual, 2)
	})
}

func TestDivision(t *testing.T) {
	Convey("将两个数相除", t, func() {
		// 不同的测试情况，请使用嵌套的Convey方法
		Convey("除数不为0", func() {
			r, err := Division(4, 2)
			So(r, ShouldEqual, 2)
			So(err, ShouldBeNil)
		})
		Convey("除数为0", func() {
			_, err := Division(4, 0)
			So(err, ShouldNotBeNil)
		})
	})
}
```

### 断言函数索引

[Assertions · smartystreets/goconvey Wiki (github.com)](https://github.com/smartystreets/goconvey/wiki/Assertions)

### 使用go test原生命令测试

```shell
$ go test    
# 四个测试方法，共有四个测试结果
.   # 代表一个So判断
1 total assertion 

. 
2 total assertions

.
3 total assertions

... # 代表三个So判断
6 total assertions

PASS
ok      notes-golang/goconvey   0.693s
```

使用`go test -v`，查看更详细的结果：

```shell
$ go test -v                  
=== RUN   TestAdd

  将两个数相加 ✔  # ✔ 代表So的通过次数


1 total assertion

--- PASS: TestAdd (0.00s)
=== RUN   TestSubtract

  将两个数相减 ✔


2 total assertions

--- PASS: TestSubtract (0.00s)
=== RUN   TestMultiply

  将两个数相乘 ✔


3 total assertions

--- PASS: TestMultiply (0.00s)
=== RUN   TestDivision

  将两个数相除 
    除数不为0 ✔✔  # ✔ 代表So的通过次数，有可能为✘，就代表So不通过的个数
    除数为0 ✔


6 total assertions

--- PASS: TestDivision (0.00s)
PASS
ok      notes-golang/goconvey   0.236s
```

### 使用goconvey提供的工具测试

```shell
$ goconvey                                      
2021/12/15 10:30:32 goconvey.go:116: GoConvey server: 
2021/12/15 10:30:32 goconvey.go:121:   version: v1.7.2      gomodifytags   goplay         gotests                                                            
2021/12/15 10:30:32 goconvey.go:122:   host: 127.0.0.1
2021/12/15 10:30:32 goconvey.go:123:   port: 8080
2021/12/15 10:30:32 goconvey.go:124:   poll: 250ms
2021/12/15 10:30:32 goconvey.go:125:   cover: true
2021/12/15 10:30:32 goconvey.go:126: 
2021/12/15 10:30:32 tester.go:19: Now configured to test 10 packages concurrently.
2021/12/15 10:30:32 goconvey.go:243: Serving HTTP at: http://127.0.0.1:8080
2021/12/15 10:30:32 goconvey.go:146: Launching browser on 127.0.0.1:8080
2021/12/15 10:30:32 integration.go:122: File system state modified, publishing current folders... 0 3279069840
2021/12/15 10:30:32 goconvey.go:159: Received request from watcher to execute tests...
2021/12/15 10:30:32 goconvey.go:154: 
2021/12/15 10:30:32 executor.go:69: Executor status: 'executing'
2021/12/15 10:30:32 coordinator.go:46: Executing concurrent tests: notes-golang/goconvey
2021/12/15 10:30:32 parser.go:24: [passed]: notes-golang/goconvey
2021/12/15 10:30:32 executor.go:69: Executor status: 'idle'
```

![image-20211215103054231](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-f2d93511789e20e80889e82f395afd4be13043aa%2Fimage-20211215103054231.png?alt=media)

测试覆盖率，点击可以查看已覆盖以及未覆盖的go代码：

![image-20211215103354337](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-6a2b7d4935c139949f9d0ad43ce0ed5ba3662ddb%2Fimage-20211215103354337.png?alt=media)

![image-20211215103443087](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-629698b9bbd59b06d77ad93dc0d182503826f092%2Fimage-20211215103443087.png?alt=media)

#### 全自动编译测试

1. 服务启动后，会在后台监测代码的变动并重新进行单元测试。如果自动编译测试不生效可以使用刷新按钮强制刷新
2. 如果想要暂停自动编译测试，可以使用暂停按钮暂停

![image-20211215103731421](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-0e9fa8beacd7dbac4eb3c67351db3eea862a6e60%2Fimage-20211215103731421.png?alt=media)

#### 测试失败的展示

![image-20211215104026964](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-9be135a388ff734a675023177747862e7391c52c%2Fimage-20211215104026964.png?alt=media)

#### 半自动化书写测试用例

![image-20211215104423294](https://2351062869-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F7b2CdwBN9liniVJpfEAc%2Fuploads%2Fgit-blob-059a747e139f8d1468ac5646458a102a64ed9a5c%2Fimage-20211215104423294.png?alt=media)
