首页 > 其他分享 >Golang单元测试

Golang单元测试

时间:2023-04-27 12:45:35浏览次数:44  
标签:测试 单元测试 Golang test go Go math

1. 前言

原文: How To Write Unit Tests in Go

Author: Tobi Balogun

译者:philoenglish.com团队; 更多资讯可访问philoenglish.com

单元测试是一种软件测试方法, 用于测试代码的最小可测试单元, 通常是函数或方法. 它的目的是确保每个单元都能够按照预期工作, 并且在修改代码时不会破坏现有的功能,它们是Go编程语言的关键部分。
在本教程中,您将创建一个小程序,然后使用Go的测试包和Go test命令对代码运行一系列测试。完成教程后,您将拥有一个可工作的单元测试套件,其中包括一个基于表表格的单元测试、一个覆盖率测试、一项基准测试和一个文档化的示例。

2. 先决条件

要完成本教程,您需要以下内容:
熟悉Golang编程语言。访问我们的教程系列/电子书《如何在Go中编码》,了解该语言的大致介绍。
Go 1.11或更高版本安装在您的本地机器上。您可以按照以下说明在Linux
macOS
Windows上安装Go。在macOS上,您也可以使用Homebrew软件包管理器安装Go。

注意:本教程使用Go module,这是Go 1.11版本中引入的包管理系统。Go module旨在取代GOPATH,并从Go 1.13版本开始成为默认选项。要想更全面地了解Go模块和GOPATH之间的差异,请考虑阅读Go核心团队的这篇官方博客文章
本教程使用Go 1.14版本进行了测试

3. 创建单元测试的示例程序

在这一步中,您将构建一个将两个整数相加的小程序。在接下来的步骤中,您将使用go test来测试程序。

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

切换到新目录中:

mkdir ./math

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

cd ./math

现在,使用您喜欢的文本编辑器,创建一个名为math.go的新文件并添加如下代码:
math.go

package math

// Add is our function that sums two integers
func Add(x, y int) (res int) {
    return x + y
}

// Subtract subtracts two integers
func Subtract(x, y int) (res int) {
    return x - y
}

在这里,我们创建了两个名为Add和Subtract的函数。每个函数接受两个整数,并返回它们的和(func Add)或差(func Subtract)。

在这一步中,我们用Go编写了一些代码。在接下来的步骤中,我们将编写一些单元测试,以确保代码正常工作。

4. 创建单元测试

在这一步中,我们将用Go编写第一个单元测试。在Go中编写测试需要一个测试文件,该测试文件必须始终以_test.Go结尾。按照惯例,Go测试文件总是位于测试代码所在的同一文件夹或包中。当您运行go build命令时,这些文件不会被编译,所以您不必担心它们最终会被发布到生产环境。
与Go中的所有内容一样,Go语言对测试有自己的看法。Go语言提供了一个小而完整的Testing包,开发人员可以将其与Go test命令一起使用。Testing包提供了一些有用的约定,例如覆盖率测试和基准测试,现在我们将对此进行探索.

创建测试类math_test.go如下:
./math/math_test.go


package math

import "testing"

func TestAdd(t *testing.T){

    got := Add(4, 6)
    want := 10

    if got != want {
        t.Errorf("got %q, wanted %q", got, want)
    }
}

Go中的测试函数包含以下签名:func TestXxxx(t*testing.t)。这意味着所有测试函数都必须以单词test开头,后跟一个后缀,后缀的第一个单词是大写的。Go中的测试函数只接收一个类型为testing.T的指针的参数。这种类型包含很多有用的方法,例如输出结果、将错误打印到屏幕和发出故障信号等等。
示例中的TestAdd用于测试add方法, 其中Test必须大写, 该方法有且仅有一个类型为*testing.T的参数, 这是go的测试规范所约定的
相应的我们需要引入testing包

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

5. 使用go test运行测试

在这一步中,我们将测试代码。go test是一个功能强大的子命令,可以帮助我们自动化测试。go test接受不同的标志,这些标志可以配置您希望运行的测试、测试返回的详细程度等等。

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

go test

运行测试命令后我们会看到如下输出

Output
PASS
ok      ./math 0.988s

go test子命令只查找后缀为_test.go的文件。找到测试文件扫描这些文件中的特殊函数,包括func TestXxx和我们将在后面的步骤中介绍的其他几个函数。go test然后生成一个临时的main package,该包以正确的方式调用这些函数,构建并运行它们,报告结果,最后清理所有内容。

对于我们的小程序来说,我们的go测试可能已经足够了,但有时我们会希望看到更多信息例如哪个测试方法被执行, 用了多长时间之类的。此时添加-v选项获得更多信息。使用-v选项运行测试:

go test -v

运行测试命令后被测试的方法, 耗时多少等等信息

Output
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      ./math 1.410s

在这一步中,我们使用go test子命令运行了一个基本的单元测试。在下一步中,我们将编写一个更复杂的、表格驱动的单元测试。

6. Table-driven的单元测试

Table-driven测试类似于基本单元测试,只是它维护一个包含不同值和结果的表。测试套件对这些值进行遍历,并将它们提交给测试代码。使用这种方法,我们可以测试输入的多种组合及其各自的输出。
现在,我们将用一个数组来定义测试用例,其字段包括Add函数所需的两个参数(两个整数)和预期结果(它们的和)。

重写./math/math_test.go


package math

import "testing"

// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
type addTest struct {
    arg1, arg2, expected int
}

var addTests = []addTest{
    addTest{2, 3, 5},
    addTest{4, 8, 12},
    addTest{6, 9, 15},
    addTest{3, 10, 13},
    
}


func TestAdd(t *testing.T){

    for _, test := range addTests{
        if output := Add(test.arg1, test.arg2); output != test.expected {
            t.Errorf("Output %q not equal to expected %q", output, test.expected)
        }
    }
}

在这里,我们定义了一个数组,填充了一个包含Add函数的参数和预期结果表格,然后编写了一个新的TestAdd函数。在这个新函数中,您对表进行遍历,运行参数,将输出与每个预期结果进行比较,然后在出现任何错误时返回这些错误。
现在使用-v标志运行测试:


go test -v

输出结果如下


Output
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
PASS
ok      ./math 1.712s

在这一步中,我们编写了一个表格驱动的单元测试。在下一步中,您将编写输出一份单元测试覆盖率的report。

7. 测试覆盖率

在这一步中,我们将在Go test中输出覆盖率。在编写测试时,了解测试覆盖了多少实际代码通常很重要。这通常被称为测试覆盖率。这也是为什么我们没有为Subtract函数编写测试的原因,这样我们就可以查看不完整的覆盖率测试。
运行以下命令来来看空当前单元测试的覆盖率:


go test -coverprofile=coverage.out

从输出结果我们可以看到测试覆盖率是50%


Output
PASS
coverage: 50.0% of statements
ok      ./math 2.073s

Go将此覆盖率数据保存在文件coverage.out中。现在您可以在web浏览器中显示结果。
运行以下命令:


go tool cover -html=coverage.out

在这一步中,您测试了表驱动单元测试的覆盖率。在下一步中,您将对您的功能进行基准测试。

8. Go基准测试

在这一步中,我们将在Go中编写一个基准测试。基准测试用于衡量功能或程序的性能。方便我们比较和了解修改对性能的影响。使用这些信息,我们可以揭示Go源代码中值得优化的部分。
在Go中,采用func BenchmarkXxx(*testing.B)形式的函数被视为基准测试。go test将在您提供-beach选项时时执行这些基准测试。基准测试是按顺序运行的。

让我们在单元测试中添加一个基准测试。

修改./math/math_test.go添加一个为BenchmarkAdd的基准测试


func BenchmarkAdd(b *testing.B){
    for i :=0; i < b.N ; i++{
        Add(4, 6)
    }
}

基准测试函数将会运行目标代码b.N次,其中N是一个可以调整的整数。在基准测试执行期间,调整b.N,直到基准测试函数持续足够长的时间以可靠地计时。--bench标志以正则表达式的形式接受其参数。

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

go test -bench=.

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

您还可以显式地指定要运行哪个基准函数, 也可以用正则表达式指定一批基准测试函数:

go test -bench=Add

运行以上任一命令,您将看到如下输出:

Output
goos: windows
goarch: amd64
pkg: math
BenchmarkAdd-4          1000000000               1.07 ns/op
PASS
ok      ./math 2.074s

这个输出的告诉我们Add方法被执行了1000000000, 每次平均耗时1.07 ns(纳秒)

现在,您已经为正在增长的单元测试添加了一个基准测试。在下一步也是最后一步中,您将在文档中添加示例,go test也将对这些示例进行评估。

9. 为代码写示例

在此步骤中,我们将为Go代码创建文档示例,然后测试这些示例。Go非常专注于正确的文档,示例代码为文档和测试增加了另一个维度。示例基于现有方法和函数。您的示例应向用户展示如何使用特定代码。示例函数是 go test 子命令专门处理的第三种类型的测试函数。

这次我们需要把fmt包添加到import列表中,并在文件末尾添加示例函数ExampleAdd:


package math

import (
    "fmt"
    "testing"
)

// arg1 means argument 1 and arg2 means argument 2, and the expected stands for the 'result we expect'
type addTest struct {
    arg1, arg2, expected int
}

var addTests = []addTest{
    addTest{2, 3, 5},
    addTest{4, 8, 12},
    addTest{6, 9, 15},
    addTest{3, 10, 13},
}

func TestAdd(t *testing.T) {

    for _, test := range addTests {
        if output := Add(test.arg1, test.arg2); output != test.expected {
            t.Errorf("Output %q not equal to expected %q", output, test.expected)
        }
    }
}

func BenchmarkAdd(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Add(4, 6)
    }
}


func ExampleAdd() {
    fmt.Println(Add(4, 6))
    // Output: 10
}

现在ExampleAdd不仅是一个测试, 也是一份关于Add方法的文档. 而且任何时候Add方法的行为发生改变, 示例代码也会被相应的修改.
这样的好处就是任何时候, 文档都会随着代码行为的改变而更新, 如果不及时更新测试就不会通过, 这也是golang对测试的独特理解.

现在重新运行单元测试:


go test -v

我们将会看到如下输出


Output
=== RUN   TestAdd
--- PASS: TestAdd (0.00s)
=== RUN   ExampleAdd
--- PASS: ExampleAdd (0.00s)
PASS
ok      ./math   0.442s

您的示例现在也经过了测试。此功能改进了您的文档,也使您的单元测试更加健壮。

10. 总结

在本教程中,我们创建了一个小程序,然后编写了一个基本的单元测试来检查其功能。然后,您将单元测试重写为基于表的单元测试,然后添加覆盖率测试、基准测试和文档示例。
作为一名程序员,花时间编写足够的单元测试对你来说很有用,因为它可以提高你对所编写的代码或程序将继续按预期工作的信心。Go中的测试包为您提供了相当多的单元测试功能。要了解更多信息,请参阅Go的官方文档。
如果你想了解更多关于围棋编程的知识,请访问我们的教程系列/免费电子书《How To Code in Go》

11. 参考文档

How To Write Unit Tests in Go

标签:测试,单元测试,Golang,test,go,Go,math
From: https://www.cnblogs.com/guoapeng/p/17358594.html

相关文章

  • golang1.6版本json包解析嵌套指针的问题小记
    指针的指针问题本地跑的好好的,测试环境跑的好好,预发布环境(准线上环境),跪了。起因就是:1a:=&struct{s:""}2json.Unmarshal([]byte{},&a)3fmt.Println(a.s)//报错行第一行代码进行&取地址,获得指针变量。第二行代码,进行json解析的时候,传入了&a, 指针的指针,a到了jso......
  • Golang 并发&同步的详细原理和使用技巧
    Golang并发概要说明并发模型Golang的并发模型属于一种很典型的CSP(communicatingsequentialprocesses)并发模型,其核心是不要通过共享内存来通信,而应该通过通信来共享内存。具体实现,就是通过goroutine来实现并发,然后并发的goroutine之间通过Channel来进行通信;为此,Gola......
  • Golang - 5 Golang的流程控制:if/else、for、switch
    5流程控制目录5流程控制1if/else1.1语法2for2.1语法2.2简单写法与实现while的功能2.3基于迭代的循环、基于索引的循环3switch3.1switch的基本使用3.2各种形式1if/else1.1语法 //基本形式if条件1{ }else条件2{ }else{ }多个分支age:=......
  • Android进阶之路 - Java 单元测试
    在此之前,我在单元测试的时候,往往会单独创建一个Demo去进行功能实现,这俩天正好闲下来,所以快速的掌握了一下这个知识点,挺简单的,下面看图说话,看完你就出师了Lookhere~:此文讲的并不高深,扩展也有限,我的目的仅仅是初步且快速的掌握单元测试使用方式,从而提升自己的开发效率~单元......
  • Kotlin进阶指南 - 单元测试
    为了减少一些功能繁琐的测试流程,单元测试是提升开发效率的有效方式之一在早些年的时候我有记录过一篇Android使用单元测试,只不过当时更多的针对Java方面的单元测试;在使用Kotlin后,我发现单元测试有点不同,好像又没什么改变,故此直接记录一篇针对Java、Kotlin都可以使用的......
  • loopback4:单元测试冻结时间
    解决方案import{expect}from'@loopback/testlab';importsinonfrom'sinon';describe('exampletest',()=>{letclock:sinon.SinonFakeTimers;before(()=>{clock=sinon.useFakeTimers();});after(()=>......
  • golang -WARNING: undefined behavior - version of Delve is too old for Go version
    1.背景启动警告 这是idea内置的dlv.exe调试器版本太低了2.解决安装最新的goinstallgithub.com/go-delve/delve/cmd/dlv@latest安装成功后,在golang的安装位置多出来个新的dlv.exe  idea打开配置 写上自己的地址即可下面是我的 重启idea生效......
  • 关于golang线程安全
    最近在字节面试,面试有一个提问:golang中的string赋值是线程安全的吗?如果是,怎么验证,如果不是,怎么验证第一反应,golang的string底层结构:typestringStructstruct{strunsafe.Pointerlenint}其中str是一个不变数组,所以该变字符串的内容都会重新生成一个底层数组,但......
  • 引用 maxmind golang 库导致的程序无法 recover crash 的问题
    新做的Gateway程序打算使用一个maxmind第三方库来解析地理信息,想了一下比较简单找了一个库直接使用。项目跑了一天得到了一堆panic,程序崩溃超过1s丢了不少数据。 从stack信息可以看到调用amxminddb-golang这个库的readLeft出现了错误,最后抛出了一个unexceptedf......
  • golang 使用 net包实现 tcp server 示例
    之前用到golang进行网络编程时,主要就是使用net/http和web框架gin,这些网络库的底层其实也还是用的标准库自带的net包,很多是对路由或者其他做封装,而且golang本身的长处之一也是网络IO的处理,这也得益于其底层的IO模型,今天我们分享的是基于TCPserver/client的简单实现,后......