GO单元测试-工具

news/2025/4/1 17:42:10/文章来源:https://www.cnblogs.com/dereklovecc/p/18798885

GO单元测试

摘要

本文介绍如何在GO语言中编写单元测试,主要内容包括:标准库中的testing包,第三方框架testify和mockery工具,monkey patching框架gomonkey,以及如何查看覆盖率。

1. 标准库中的测试框架

GO标准库中提供了做单元测试的工具:testing包以及go test命令工具。在编写测试代码时,对文件名和函数名有一定的要求,文件名必须遵循*_test.go的形式, 测试函数名则必须以Test开头,并且必须接收一个testing.T类型的参数。通常针对每个被测go文件创建一个对应的_test.go文件,并在其中编写相关的测试代码,测试函数的名字为TestXXX,XXX为被测函数的名字。下面以一个简单的例子说明如何编写,运行测试。

1.1 编写测试代码

比如,我们有一个code.go文件,其中包含了我们需要测试的代码,具体内容如下:

// code.go file
package utfunc 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 Divide(a, b int) int {return a / b
}

为了给其中的Add函数编写单元测试,我们首先创建一个名为code_test.go的文件,并在其中编写测试代码如下:

// code_test.go file
package utimport ("testing"
)func TestAdd(t *testing.T) {if Add(1, 2) != 3 {t.Errorf("Add(1, 2) should return 3, but it returned %d", Add(1, 2))}
}

TestAdd函数有一个参数:t,其类型为*testing.T, t提供了一些方法来帮助我们进行测试,如Errorf方法,可以用来输出错误信息,Run方法可以用来运行测试用例,并输出测试结果。

1.2 运行测试

在命令行中,通过go test ./...命令,我们可以运行所有的测试用例,并输出测试结果如下。

$ go test ./...
?       learn_go        [no test files]
?       learn_go/reflect_learn  [no test files]
ok      learn_go/ut     1.510s

如果测试用例中有错误,则会输出如下的错误信息:

$ go test ./...
?       learn_go        [no test files]
?       learn_go/reflect_learn  [no test files]
--- FAIL: TestAdd (0.00s)code_test.go:9: Add(1, 2) should return 3, but it returned 4
FAIL
FAIL    learn_go/ut     1.539s
FAIL

如果想要获取详细的输出信息,可以加上-v参数,即:go test -v ./...

如果只想运行某个包下面的测试用例,可以加上包名,如:go test learn_go/ut,其中learn_go/ut是包的完整名字。

此外,在VS Code中,可以通过直接点击测试函数名字上面的运行按钮来运行测试用例,请参考下图。

2. 第三方测试库testify

大部分情况下,GO标准库提供的测试工具已经够用了,但有些时候,我们需要更加灵活的测试工具,比如:

  1. 更加方便的判断语句,而不是需要先用if语句判断,然后再用Errorf输出错误信息
  2. 对一些接口进行mock

这时候,我们可以选择第三方的mock框架,比如testify, 其github地址为: https://github.com/stretchr/testify

  • 下面说明testify的使用方法
  1. 安装testify
go get github.com/stretchr/testify
  1. 编写测试代码

下面的测试函数TestSubtract使用assert.Equal方法来判断Subtract函数的返回值是否正确,更加方面。testify还提供了其他的断言方法,比如assert.Error, assert.Errorf, assert.Contains, assert.Nil等。

// code_test.go file
package utimport ("testing""github.com/stretchr/testify/assert"
)func TestSubtract(t *testing.T) {assert.Equal(t, -1, Subtract(1, 2))
}
  1. 运行测试

通过添加 -run ^TestSubtract$参数,我们可以只运行TestSubtract函数,如下:

$ go test -run ^TestSubtract$ learn_go/ut
ok      learn_go/ut     0.614s
  • 使用testify编写mock测试

在编写测试的过程中,我们可能需要mock一些接口,比如数据库操作,网络请求等,从而消除副作用(side effect),提高测试的可靠性。testify中的mock包可以帮助我们生成mock对象。下面举例说明。

// code.go file
type Stock interface {GetPrice(t time.Time) float64
}type ShangHaiStock struct {
}func (s ShangHaiStock) GetPrice(t time.Time) float64 {if t.IsZero() {t = time.Now()}return getPriceFromStockExchangeServer(t, "shanghai")
}func BuyStock(s Stock, amount int) float64 {price := s.GetPrice(time.Now())return float64(amount) * price
}

在上面这段代码中,我们首先声明了一个Stock接口,接口中的GetPrice函数会根据时间获取股票价格,结构体ShangHaiStock实现了该Stock接口。

ShangHaiStock的GetPrice函数会通过网络请求从上交所获取股票价格,具体请求操作在getPriceFromStockExchangeServer函数中实现。

BuyStock函数会根据传入的Stock接口,获取当前的股票价格,并计算出购买股票的总价。

下面,我们为BuyStock函数编写测试代码,为了隔离对外部网络请求的依赖,我们需要先创建一个MockStock结构体,并让它实现Stock接口,然后在测试中使用MockStock的对象。具体代码如下:

// code_test.go file
package utimport ("testing""time""github.com/stretchr/testify/assert""github.com/stretchr/testify/mock"
)type MockStock struct {mock.Mock
}func (m *MockStock) GetPrice(t time.Time) float64 {args := m.Called(t)return args.Get(0).(float64)
}func TestBuyStock(t *testing.T) {stockPrice := 20.2stockCount := 100stock := &MockStock{}stock.On("GetPrice", time.Now()).Return(stockPrice)paidAmount := BuyStock(stock, stockCount)assert.Equal(t, stockPrice*float64(stockCount), paidAmount)
}

MockStock结构体继承了mock.Mock,在其GetPrice函数中,我们使用m.Called方法获取调用参数,并返回预期的值。

在测试函数TestBuyStock中,我们先创建一个MockStock对象,并使用On方法指定GetPrice函数的调用参数和返回值,然后调用BuyStock函数,并断言其返回值是否正确,这里需要注意,语句stock.On("GetPrice", time.Now()).Return(stockPrice)中的Return方法将股票的价格固定为stockPrice,即20.2。

下面是运行测试的结果:

$ go test -v -run ^TestBuyStock$ learn_go/ut
=== RUN   TestBuyStock
--- PASS: TestBuyStock (0.01s)
PASS
ok      learn_go/ut     0.656s

3. 第三方工具mockery

在使用testify的mock功能时,我们需要先定义一个继承自mock.Mock的结构体,并让该结构体实现依赖的接口,如果接口中的方法不是很多,我们尚可手工实现,但是如果接口的方法很多,我们就需要使用mockery工具来自动生成mock对象。

mockery是一个开源的工具,可以根据接口定义文件生成mock对象,其github地址为:https://github.com/vektra/mockery

  1. 安装mockery

具体安装方法请参考 https://vektra.github.io/mockery/latest/installation/

安装完成之后,使用 mockery --version, 确认安装是否成功。

  1. 使用mockery生成mock对象

执行如下命令,为Stock接口生成mock对象:

mockery --dir=ut --name=Stock

如果命令执行成功,会在当前目录下生成一个mocks目录,其中包含了Stock接口的mock对象,内容如下图:

我们可以看到,mockery帮我们自动创建了Stock结构体,并实现了GetPrice方法,我们只需要在测试代码中使用该对象,即可隔离对外部网络请求的依赖。

使用mockery,我们可以很方便地为自定义的接口创建mock结构体,但是对于外部的接口,如第三方库,该怎么办呢?一个比较取巧的办法是:重新创建一个接口继承该接口,比如对于标准库里的context.Context接口,我们可以如下创建一个MockedContext接口,然后使用mockery针对MockedContext生成mock结构体。

type MockedContext interface {context.Context
}

4. 第三方monkey patching库gomonkey

通过testify和mockery,我们可以很方便地mock接口,那我们如何mock结构方法和普通函数(如json.Marshal)呢?gomonkey库可以帮助我们做到这一点。

gomonkey是一个开源的库,可以帮助我们mock结构方法和普通函数,其github地址为:https://github.com/agiledragon/gomonkey ,下面我们来看看如何使用。

  1. 安装gomonkey

具体安装方法请参考 https://github.com/agiledragon/gomonkey#installation

$ go get github.com/agiledragon/gomonkey/v2@v2.13.0
  1. 使用gomonkey拦截普通方法:json.Marshal
func TestGomonkeyPatchingForJsonMarshal(t *testing.T) {marshedBytes := []byte(`{"name": "Alice", "age": 25}`)patches := gomonkey.ApplyFunc(json.Marshal, func(v any) ([]byte, error) {t.Log("Mocked json.Marshal called with args:", v)return marshedBytes, nil}) // replace json.Marshal with a mock functiondefer patches.Reset() // reset the mock function after testresult, err := json.Marshal("")assert.Nil(t, err)assert.Equal(t, marshedBytes, result)
}

在测试函数TestGomonkeyPatchingForJsonMarshal中,我们使用gomonkey.ApplyFunc方法,将json.Marshal函数替换为一个mock函数,该函数会打印日志,并返回固定的字节数组:marshedBytes。

在defer语句中,我们使用patches.Reset方法,恢复json.Marshal的原始功能。

最后使用assert.Nil和assert.Equal方法,验证mock函数是否生效,即json.Marshal("")的返回值是否等于marshedBytes。

  1. 使用gomonkey拦截结构方法
func TestGomonkeyPatchingForStructMethod(t *testing.T) {numberList := list.New()numberList.PushBack(1)numberList.PushBack(2)assert.Equal(t, 2, numberList.Len())patches := gomonkey.ApplyMethod(reflect.TypeOf(numberList), "Len", func(list *list.List) int {return 0}) // replace numberList.Len with a mock functiondefer patches.Reset() // reset the mock function after testassert.Equal(t, 0, numberList.Len())
}

在测试函数TestGomonkeyPatchingForStructMethod中,我们使用gomonkey.ApplyMethod方法,将numberList.Len方法替换为一个mock函数,该mock函数会返回0。

在defer语句中,我们使用patches.Reset方法,恢复numberList.Len的原始功能。

最后,使用assert.Equal方法,验证numberList.Len方法是否返回0,即使它实际包含两个元素。

5. 查看覆盖率

在编写测试代码时,我们需要尽量保证较多的代码被测试到,代码测试覆盖率就是衡量多少代码被覆盖到的指标。下面看下如何生成测试覆盖率报告。

5.1 生成覆盖率数据

通过go test命令加上-coverprofile参数,可以生成测试覆盖率数据。下面的命令会生成一个cover.out文件,里面包含了测试覆盖率数据。

$ go test ./... -coverprofile cover.out

5.2 生成覆盖率报告

我们可以使用go tool cover 根据cover.out文件生成测试覆盖率报告,下面的命令会生成一个cover.html文件,里面包含了测试覆盖率报告。

$ go tool cover -html cover.out -o cover.html

打开cover.html文件,内容如下图:

5.3 忽略测试文件

有时,我们可能需要忽略一些测试文件,比如一些测试文件中的代码没有被测试到,或者一些测试文件中的代码没有达到测试覆盖率要求。

我们可以在go test命令中使用-coverpkg参数,指定测试覆盖率数据中包含哪些包。

$ go test ./... -coverprofile cover.out -coverpkg=./...

-coverpkg参数的值为./...,表示包含当前目录及其子目录下的所有包。

总结

本文介绍了GO语言中编写单元测试的基本方法,包括标准库中的testing包,第三方框架testify和mockery工具,以及Monkey patching框架gomonkey。希望能对你有所帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/908120.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

36.7K star!拖拽构建AI流程,这个开源LLM应用框架绝了!

`Flowise` 是一款革命性的低代码LLM应用构建工具,开发者通过可视化拖拽界面,就能快速搭建基于大语言模型的智能工作流。该项目在GitHub上线不到1年就斩获**36.7K星标**,被开发者誉为"AI时代的乐高积木"。36.7K star!拖拽构建AI流程,这个开源LLM应用框架绝了! 只…

Tandis 解法集()

1-x 的关卡都比较简单。 2-1: 现在我们有一个常驻的旋转操作。 和某个 1-x 的关卡一样处理即可。 2-2: 开始起飞了。 操作类似于一个极坐标变换:把方块平放即可得到圆。 2-3如果要通过这种变换得到一个中心对称图形,那左侧放的一定也是长度恰好为 8 的倒下的柱体。 而柱体的切…

Java24发布,精心总结

Java 24作为2025年3月发布的最新版本,延续了Java平台每半年发布一次的节奏,带来了24项重要改进。本文将按照核心改进领域分类,详细解析每个特性的技术原理和实际价值,帮助开发者全面了解这一版本的能力边界和应用场景。 不过Java24是自Java 21 以来的第三个非长期支持版本,…

一台电脑上快速切换git账号

如果你的一台笔记本,既要开发公司的项目,同时你又要参与github,或者是gitee上的开源项目。你就需要使用不同的账号来提交代码。如何快速、高效的切换和管理不同的git账号? 本人使用的就是这种方式,只要配置好,会自动切换的。本文来自博客园,作者:Eular,转载请注明原文…

从 0 到 1 打造代码扫描工具:实战指南与技术解析

在团队协作开发的场景中,代码规范的重要性不言而喻。当团队规模逐渐扩大,如何确保每个人提交的代码都符合规范,比如不能 import *、代码嵌套不能超3层,代码包层级依赖结构约定、 不能修改核心文件等,成为了一个亟待解决的问题。今天,咱们就来聊聊如何开发一个代码扫描工具…

深入解析Java Web开发中的异常处理机制:策略、实践与案例分析

一、引言 1. Java Web开发概述 Java Web开发是基于Java语言构建网络应用程序的过程,它通过Java Servlet、JSP(JavaServer Pages)、Spring MVC等技术,实现动态网页的生成和交互。Java Web应用广泛应用于企业级系统、电子商务平台、在线教育等领域。在这些应用中,用户通过浏…

Java Web开发中的请求与响应机制

一、Java Web开发基础概念 (一)Java Web开发概述 Java Web开发是指使用Java语言及相关技术开发基于Web的应用程序。它主要通过Java Servlet、JSP(JavaServer Pages)、Spring MVC等技术实现客户端与服务器之间的交互。Java Web应用广泛应用于企业级应用、电子商务平台、在线…

GUI猜数字

序言 本文将会介绍“GUI猜数字”这款原创软件的界面、功能、编译等。详见后文~下载地址开始前先晾出下载地址: https://biaozyx.lanzouq.com/i31nk2rcftsj (提取码:guiGN)内容展示 所含文件 GuessNumber.exe(Windows可执行程序) GuessNumber.py(源代码,可用后面教程编译…

Apple Final Cut Pro 11.1 - 专业后期制作 (视频剪辑)

Apple Final Cut Pro 11.1 - 专业后期制作 (视频剪辑)Apple Final Cut Pro 11.1 - 专业后期制作 (视频剪辑) Final Cut Pro 11.1.0 + Compressor 4.10.0 + Motion 5.10.0 请访问原文链接:https://sysin.org/blog/apple-final-cut-pro/ 查看最新版。原创作品,转载请保留出处。…

Adobe Photoshop 2025 v26.5 (macOS, Windows) - 照片和设计软件

Adobe Photoshop 2025 v26.5 (macOS, Windows) - 照片和设计软件Adobe Photoshop 2025 v26.5 (macOS, Windows) - 照片和设计软件 Acrobat、After Effects、Animate、Audition、Bridge、Character Animator、Dimension、Dreamweaver、Illustrator、InCopy、InDesign、Lightroo…

VMware Aria Operations 8.18.3 新增功能简介

VMware Aria Operations 8.18.3 新增功能简介VMware Aria Operations 8.18.3 - 多云 IT 运维管理 通过统一的高性能平台,实现跨私有云、混合云和多云环境的 IT 运维管理。 请访问原文链接:https://sysin.org/blog/vmware-aria-operations/ 查看最新版。原创作品,转载请保留出…

CCF的GESP等级考试与CSP-J/S竞赛

CCF的GESP等级考试与CSP-J/S竞赛介绍一、GESP介绍 GESP 即 CCF 编程能力等级认证,由中国计算机学会发起并主办,是为青少年计算机编程学习者提供能力验证的线下平台. 其目的是提升青少年计算机和编程教育培训水平,推广和普及青少年计算机和编程教育,选拔优秀人才. 它的适用年…