go代码测试综合指南(三)

Testing 软件包

testing
 软件包在 Go 测试中发挥着关键作用。 它让开发者能够使用不同类型的测试函数创建单元测试。 
testing.T
 类型提供了控制测试执行的方法,例如使用 
Parallel()
 并行运行测试,使用 
Skip()
 跳过测试,以及使用 
Cleanup()
 调用测试拆解函数。

错误和日志

testing.T
 类型提供了多种与测试工作流交互的实用工具,包括 
t.Errorf()
,它会输出错误消息并将测试设为失败。

务必需要注意的是,
t.Error*
 不会停止测试的执行。 测试完成后,所有遇到的错误都将被报告。 有时,执行失败更有意义,在这种情况下,您应该使用 
t.Fatal*
。 测试执行期间,使用 
Log*()
 函数输出信息可能会很方便:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
```
func TestFooer2(t *testing.T) {
    input := 3
    result := Fooer(3)
    t.Logf("The input was %d", input)
    if result != "Foo" {
        t.Errorf("Result was incorrect, got: %s, want: %s.", result, "Foo")
    }
    t.Fatalf("Stop the test now, we have seen enough")
    t.Error("This won't be executed")
}
```

```

输出提示应如下所示:

![](/images/blog/1742927620175_image_1.png)

如图所示,最后一行
` t.Error("This won't be executed")`
 已被跳过,因为 
`t.Fatalf`
 已经终止了此测试。

### 运行并行测试

测试默认按顺序运行,
`Parallel()`
 方法则指示测试应并行运行。 所有调用此函数的测试都将并行执行。 
`go test`
 通过以下方式处理并行测试:暂停各个调用 
`t.Parallel()`
 的测试,然后在所有非并行测试完成后将其并行恢复。 
`GOMAXPROCS`
 环境定义一次可以并行运行多少个测试,这个数字默认等于 CPU 的数量。

您可以构建一个并行运行两个子测试的小示例。 下面的代码将同时测试 
`Fooer(3)`
 和 
`Fooer(7)`


```go

func TestFooerParallel(t *testing.T) {
    t.Run(“Test 3 in Parallel”, func(t *testing.T) {
        t.Parallel()
        result := Fooer(3)
        if result != “Foo” {
            t.Errorf(“Result was incorrect, got: %s, want: %s.”, result, “Foo”)
        }
    })
    t.Run(“Test 7 in Parallel”, func(t *testing.T) {
        t.Parallel()
        result := Fooer(7)
        if result != “7” {
            t.Errorf(“Result was incorrect, got: %s, want: %s.”, result, “7”)
        }
    })
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
  
```

GoLand 在执行期间会为各个测试输出所有状态信息(RUN、PAUSE 或 CONT)。 运行上面的代码时,您可以清楚地看到 Test_3 在 Test_7 开始运行之前被暂停。 在 Test_7 被暂停后,两个测试都将恢复运行,直到完成。

![](/images/blog/1742927620175_image_2.png)

为了减少重复,您可能想在使用 
`Parallel()`
 时使用表驱动测试。 显然,这个示例需要复制某些断言逻辑。

### 跳过测试

使用 
`Skip()`
 方法可以将单元测试与集成测试分开。 集成测试同时验证多个函数和组件,执行起来通常较慢,因此有时更适合只执行单元测试。 例如,
`go test`
 接受旨在运行 “fast” 测试的 
`-test.short`
 标志。 然而,
`go test`
 并不决定测试是否为 “short”。 您需要结合使用 
`testing.Short()`
(使用 
`-short`
 时设为 
`true`
)和 
`t.Skip()`
,如下所示:

```go

func TestFooerSkiped(t *testing.T) {
    if testing.Short() {
        t.Skip(“skipping test in short mode.”)
    }
    result := Fooer(3)
    if result != “Foo” {
        t.Errorf(“Result was incorrect, got: %s, want: %s.”, result, “Foo”)
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
  
```

运行 
`go test -v`
 时将执行此测试,但运行 
`go test -v -test.short`
 时测试将被跳过。

如下所示,测试在 
`short`
 模式下被跳过。

![](/images/blog/1742927620175_image_3.png)

每次运行测试都编写标志可能很麻烦。 好在 GoLand 可为每个测试保存运行/调试配置。 每次使用间距中的绿色箭头运行测试都会创建一个配置。

您可以在 GoLand 的帮助中详细了解测试的配置模板。 

### 测试拆解和清理

`Cleanup()`
 方法便于管理测试拆解。 首先,可以使用 
`defer`
 关键字时,为什么需要该函数可能并不明显。

按如下所示使用 defer 解决方案:

```go

func Test_With_Cleanup(t *testing.T) {
  // Some test code
  defer cleanup()
  // More test code
}

1
2
3
4
5
6
7
8
9
10
11
  
```

虽然这很简单,但它也带来了一些问题,如这篇关于 Go 1.14 新功能的文章所述。 反对 
`defer`
 方式的主要论点是,它会使测试逻辑的设置更加复杂,并且在涉及许多组件时会使测试函数混乱。

`Cleanup()`
 函数在每个测试(包括子测试)结束时执行,并清晰显示测试的预期行为。

```go

func Test_With_Cleanup(t *testing.T) {
  // Some test code here
    t.Cleanup(func() {
        // cleanup logic
    })
  // more test code here
}

1
2
3
4
5
6
7
8
9
10
11
12
  
```

您可以阅读这篇文章中的示例详细了解测试清理。 

这时候就要提到 
`Helper()`
 方法了。 这个方法的用途是在测试失败时改进日志。 在日志中,
`helper`
 函数的行号会被忽略,只报告失败测试的行号,这有助于确定失败的测试。

```go

func helper(t *testing.T) {
  t.Helper()
  // do something
}
func Test_With_Cleanup(t *testing.T) {
  // Some test code here
    helper(t)
  // more test code here
}

1
2
3
4
5
6
7
8
  
```

最后,
`TempDir()`
 是一种自动为测试创建临时目录并在测试完成时删除该文件夹的方法,无需编写额外清理逻辑。

```go

func TestFooerTempDir(t *testing.T) {
    tmpDir := t.TempDir()
  // your tests
}

  

这个函数非常实用,但由于相对较新,许多 Go 开发者尚未了解,仍在测试中手动管理临时目录。

江达小记