如何用 Go 编写单元测试

作者选择了 FreeBSD Foundation作为 Write for Donations计划的一部分接受捐款。

介绍

单元测试是测试程序或软件包中的特定代码的函数,单元测试的任务是检查应用程序的正确性,它们是Go编程语言的重要组成部分(https://www.digitalocean.com/community/tutorial_series/how-to-code-in-go)。

在本教程中,您将创建一个小程序,然后在您的代码上运行一系列测试 使用 Go 的 testing 包和 go test 命令

前提条件

要完成本教程,您将需要以下内容:

<$>[注] **注:**本教程使用 Go 模块,这是在 Go 版本 1.11 中引入的包管理系统。Go 模块旨在取代 $GOPATH,并成为从 Go 版本 1.1 开始的默认选项。

本教程是使用 Go 版本 1.14 <$> 测试的

步骤 1 — 创建单元测试的样本程序

在你可以编写任何单元测试之前,你需要一些代码来分析你的测试. 在这个步骤中,你将构建一个小程序,总共两个整数. 在接下来的步骤中,你将使用去测试来测试该程序。

首先,创建一个名为数学的新目录:

1mkdir ./math

移动到新目录中:

1cd ./math

这将是您的程序的根目录,您将从这里运行所有剩余的命令。

现在,使用nano或您喜欢的文本编辑器,创建一个名为math.go的新文件:

1nano math.go

添加以下代码:

 1[label ./math/math.go]
 2package math
 3
 4// Add is our function that sums two integers
 5func Add(x, y int) (res int) {
 6    return x + y
 7}
 8
 9// Subtract subtracts two integers
10func Subtract(x, y int) (res int) {
11    return x - y
12}

在这里,您正在创建两个名为添加抽取的函数,每个函数接受两个整数,并返回它们的总和(添加函数)或差异(抽取函数)。

保存并关闭文件。

现在,在以下步骤中,您将写一些单元测试,以确保您的代码正常工作。

步骤2 — 写单元测试在Go

在此步骤中,你将写你的第一个测试在Go. 写测试在Go需要一个测试文件链接,这个测试文件必须总是以_test.go结束. 根据惯例,Go测试文件总是位于相同的文件夹,或包,他们正在测试的代码所在。

和 Go 中的所有东西一样,该语言对测试的看法都很明显。 [Go 语言提供了一个名为 testing(https://golang.org/pkg/testing/#hdr-Subtests_and_Sub_benchmarks)的最小但完整的包,开发人员将其与 go test 命令一起使用. testing 包提供了一些有用的惯例,如覆盖测试和基准,您现在将探索。

使用您的编辑器创建并打开名为 `math_test.go'的新文件:

1nano math_test.go

Go 中的测试函数包含此签名: func TestXxxx(t *testing.T). 这意味着所有测试函数都必须从单词 Test 开始,然后是第一个单词被加值的尾声。 Go 中的测试函数只收到一个参数,在这种情况下,它是一种类型的 testing.T 的指针。

将以下代码添加到 math_test.go:

 1[label ./math/math_test.go]
 2
 3package math
 4
 5import "testing"
 6
 7func TestAdd(t *testing.T){
 8
 9    got := Add(4, 6)
10    want := 10
11
12    if got != want {
13        t.Errorf("got %q, wanted %q", got, want)
14    }
15}

首先,您声明您要测试的包的名称,然后导入测试包本身,然后将testing.T类型以及包导出的其他类型和方法提供。

要总结一下,以下是Go中测试的特点:

  • 第一個和唯一的參數必須是 t *testing.T
  • 測試函數始於 'Test' 單詞,接著是 't.Error' 單詞或句子,以大字母為開頭 (傳統是使用測試中的方法的名稱,例如, 'TestAdd')
  • 測試呼叫 't.Error' 或 't.Fail' 表示錯誤 (您正在呼叫 't.Error' 因為它返回比 't.Fail' 更多的細節)
  • 您可以使用 't.Log' 提供不錯誤的掃描資訊
  • 測試儲存到使用這個命名傳統的檔案

保存,然后关闭文件。

在此步骤中,您在 Go 中写了第一个测试,在下一步,您将开始使用go test来测试您的代码。

步骤 3 — 测试您的Go代码使用Go测试命令

在此步骤中,您将测试您的代码。 go test 是一个强大的子命令,可以帮助您自动化测试。 go test 接受不同的旗帜,可以配置您想要运行的测试、测试返回的语音程度等等。

从项目的根目录中运行您的第一个测试:

1go test

您将获得以下输出:

1[secondary_label Output]
2PASS
3ok      ./math 0.988s

PASS 表示代码按预期运行;当测试失败时,您将看到FAIL

go test子命令只寻找具有_test.go字符串的文件。go test然后扫描这些文件(文件)以寻找特殊功能,包括func TestXxx和其他一些我们将在以后的步骤中覆盖的文件。

我们的去测试可能足够我们的小程序,但会有一些时候你会想看到哪些测试正在运行,以及每个测试需要多长时间。

1go test -v

您将看到以下结果:

1[secondary_label Output]
2=== RUN TestAdd
3--- PASS: TestAdd (0.00s)
4PASS
5ok      ./math 1.410s

在此步骤中,您使用go test子命令运行了一个基本单元测试,在下一步,您将写出一个更复杂的表驱动单元测试。

步骤 4 — 写桌面驱动的测试

一个表驱动的测试就像一个基本单元测试除非它保持了不同的值和结果的表。测试套件重复了这些值并提交给测试代码。

现在,您将用结构表替换单元测试,其字段包括添加函数所需的两个参数(两个整数)和预期结果(他们的总和)。

重新打开math_test.go:

1nano math_test.go

删除文件中的所有代码,并添加以下表驱动单元测试:

 1[label ./math/math_test.go]
 2
 3package math
 4
 5import "testing"
 6
 7// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
 8type addTest struct {
 9    arg1, arg2, expected int
10}
11
12var addTests = []addTest{
13    addTest{2, 3, 5},
14    addTest{4, 8, 12},
15    addTest{6, 9, 15},
16    addTest{3, 10, 13},
17
18}
19
20func TestAdd(t *testing.T){
21
22    for _, test := range addTests{
23        if output := Add(test.arg1, test.arg2); output != test.expected {
24            t.Errorf("Output %q not equal to expected %q", output, test.expected)
25        }
26    }
27}

在这里,您正在定义一个结构,填写一个结构表,其中包括您的添加函数的参数和预期结果,然后写一个新的测试添加函数. 在这个新函数中,您重复表,运行参数,将输出与每个预期结果进行比较,然后返回任何错误,如果它们发生。

保存并关闭文件。

现在用-v旗进行测试:

1go test -v

您将看到与以前相同的输出:

1[secondary_label Output]
2=== RUN TestAdd
3--- PASS: TestAdd (0.00s)
4PASS
5ok      ./math 1.712s

随着循环的每一次迭代,代码会对添加函数计算的值进行测试,而不是预期值。

在此步骤中,您写了一个表驱动的测试,而在下一步中,您将写一个覆盖测试。

步骤5 — 写入覆盖测试

在此步骤中,您将写入 Go 中的覆盖性测试。在编写测试时,通常重要的是知道测试涵盖了多少实际代码。

运行以下命令来计算当前单元测试的覆盖范围:

1go test -coverprofile=coverage.out

您将获得以下输出:

1[secondary_label Output]
2PASS
3coverage: 50.0% of statements
4ok      ./math 2.073s

将此覆盖数据保存到coverage.out文件中,现在您可以在 Web 浏览器中呈现结果。

运行以下命令:

1go tool cover -html=coverage.out

一个网页浏览器将打开,你的结果将显示:

go unit testing coverage.out

绿色文本表示覆盖,而红色文本则表示相反。

在此步骤中,您测试了表驱动单元测试的覆盖范围,在下一步,您将基准您的函数。

步骤6 — 写基准在行

在此步骤中,您将写入 Go 中的基准测试。 Benchmarking 衡量函数或程序的性能. 这允许您比较实现并了解您对代码所做的更改的影响。

在 Go 中,采取func BenchmarkXxx(*testing.B)的形式的函数被视为基准。

让我们为我们的单位测试添加一个基准。

打开math_test.go:

1nano math_test.go

现在使用func BenchmarkXxx(*testing.B)语法添加一个 benchamrk 函数:

1[label ./math_test.go]
2...
3func BenchmarkAdd(b *testing.B){
4    for i :=0; i < b.N ; i++{
5        Add(4, 6)
6    }
7}

基准函数必须运行目标代码 b.N 次,其中 N 是可以调节的整数。在基准执行过程中, b.N 被调节,直到基准函数持续足够长的时间可靠地计时。

保存并关闭文件。

现在让我们再次使用去测试来运行我们的基准:

1go test -bench=.

. 将匹配文件中的每个基准函数。

您还可以明确声明基准函数:

1go test -bench=Add

运行任何一个命令,你会看到这样的输出:

1[secondary_label Output]
2goos: windows
3goarch: amd64
4pkg: math
5BenchmarkAdd-4 1000000000 1.07 ns/op
6PASS
7ok      ./math 2.074s

由此产生的输出意味着循环以每循环 1.07 纳秒的速度运行 10,000,000 次。

<$>[注] **注:**尝试不要在忙碌的系统上基准你的Go代码,否则你会干扰基准过程并获得不准确的结果 <$>

在下一步和最后一步中,您将为您的文档添加示例,而去测试也将评估。

步骤7 —用示例记录您的Go代码

在此步骤中,您将 用示例记录您的 Go 代码,然后测试这些示例)。Go 非常专注于适当的文档,示例代码为文档和测试增添了另一个维度。示例基于现有的方法和功能。您的示例应该向用户展示如何使用特定代码。示例函数是第三类函数,由Go 测试子命令专门处理。

首先,重新打开math_test.go

1nano math_test.go

现在添加突出的代码. 这将添加 the fmt package 到导入列表和您的示例函数在文件的末尾:

 1[label ./math/math_test.go]
 2
 3package math
 4
 5import (
 6    "fmt"
 7    "testing"
 8)
 9
10// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
11type addTest struct {
12    arg1, arg2, expected int
13}
14
15var addTests = []addTest{
16    addTest{2, 3, 5},
17    addTest{4, 8, 12},
18    addTest{6, 9, 15},
19    addTest{3, 10, 13},
20}
21
22func TestAdd(t *testing.T) {
23
24    for _, test := range addTests {
25        if output := Add(test.arg1, test.arg2); output != test.expected {
26            t.Errorf("Output %q not equal to expected %q", output, test.expected)
27        }
28    }
29}
30
31func BenchmarkAdd(b *testing.B) {
32    for i := 0; i < b.N; i++ {
33        Add(4, 6)
34    }
35}
36
37func ExampleAdd() {
38    fmt.Println(Add(4, 6))
39    // Output: 10
40}

使用输出:行来指定和记录预期输出。

<$>[注] :比较忽略了领先和追溯空间。

保存并关闭文件。

现在重新运行您的单位测试:

1go test -v

你会看到这样的更新:

1[secondary_label Output]
2=== RUN TestAdd
3--- PASS: TestAdd (0.00s)
4=== RUN ExampleAdd
5--- PASS: ExampleAdd (0.00s)
6PASS
7ok      ./math 0.442s

您的示例现在也被测试了. 此功能可改进您的文档,并使您的单元测试更加强大。

结论

在本教程中,您创建了一个小程序,然后写了一个基本单元测试来检查其功能,然后将单元测试重写为基于表的单元测试,然后添加了一个覆盖测试,一个基准和一个文档示例。

花时间编写足够的单元测试对你来说是很有用的,因为它会提高你对你编写的代码或程序将继续按预期工作的信心。

如果你想了解更多关于编程在Go, 参见我们的教程系列 / 免费电子书, 如何编码在Go

Published At
Categories with 技术
Tagged with
comments powered by Disqus