我是一只可爱的土拨鼠,专注于分享 Go 职场、招聘和求职,解 Gopher 之忧!欢迎关注我。

欢迎大家加入Go招聘交流群,来这里找志同道合的小伙伴!跟土拨鼠们一起交流学习。

土拨鼠今天带来一篇让你在虎年如虎添翼的Go测试工具和技巧的文章分享。大体内容翻译于 Go (Golang): Testing tools & tips to step up your game[1]。文中作者介绍了不少好用的Go库和工具。作者在也已经将这篇文章中提到的所有工具和窍门投入了个人项目中,更多细节可以查看这里:https://github.com/rafael-piovesan/go-rocket-ride。大家也可以在自己的项目中尝试运用。

Testcontainers

作者在文章落笔之时,第一个想到的工具是Testcontainers-Go(https://golang.testcontainers.org/)。下面Testcontainers的介绍。

https://github.com/testcontainers/testcontainers-go 是一个Go包,它使得创建和清理基于容器的依赖关系变得简单,主要用于自动化集成/冒烟测试。清晰和易于使用的API使开发者能够以编程方式定义应该作为测试的一部分运行的容器,并在测试完成后清理这些资源。这意味着你可以在你的应用程序的集成和冒烟测试最需要的时候和地方,以某种无缝的方式,不依赖外部资源,以编程方式创建、互动和处置容器。在实践中,不再需要保留和维护各种docker-compose文件、复杂的shell脚本和其他东西。你可以根据你的需要生成许多容器,每一个都是为了一个特定的场景,只要它们对运行你的测试有必要,就可以把它们保留下来。

下面是我如何建立一个Postgres容器并用于我的集成测试(更多细节见https://github.com/rafael-piovesan/go-rocket-ride)。

// NewPostgresContainer creates a Postgres container and returns its DSN to be used
// in tests along with a termination callback to stop the container.
func NewPostgresContainer() (string, func(context.Context) error, error) {ctx := context.Background()usr := "postgres"pass := "postgres"db := "testdb"templateURL := "postgres://%s:%s@localhost:%s/%s?sslmode=disable"// Create the containerc, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{Started: true,ContainerRequest: testcontainers.ContainerRequest{Image: "postgres:14.1",ExposedPorts: []string{"0:5432",},Env: map[string]string{"POSTGRES_DB":       db,"POSTGRES_USER":     usr,"POSTGRES_PASSWORD": pass,"POSTGRES_SSL_MODE": "disable",},Cmd: []string{"postgres", "-c", "fsync=off",},WaitingFor: wait.ForSQL("5432/tcp","postgres",func(p nat.Port) string {return fmt.Sprintf(templateURL, usr, pass, p.Port(), db)},).Timeout(time.Second * 15),},})if err != nil {return "", func(context.Context) error { return nil }, err}// Find ports assigned to the new containerports, err := c.Ports(ctx)if err != nil {return "", func(context.Context) error { return nil }, err}driverURL := fmt.Sprintf(templateURL, usr, pass, ports["5432/tcp"][0].HostPort, db)return driverURL, c.Terminate, nil
}// Using it your tests
ctx := context.Background()
dsn, terminate, err := testcontainer.NewPostgresContainer()
if err != nil {log.Fatalf("cannot create database container: %v", err)
}
defer terminate(ctx)sqldb, err := sql.Open("pg", dsn)
if err != nil {log.Fatalf("cannot open database: %v", err)
}

上面这段代码就是创建Postgres测试容器的实用代码

如果你觉得这个很有趣,那么也可以看看这两个其他项目。

  • Dockertest (https://github.com/ory/dockertest),旨在提供与Testcontainers-Go所提供的相同的易于使用、更广泛和通用的API。

  • Gnomock (https://github.com/orlangure/gnomock), 无需使用临时 Docker 容器编写模拟即可测试您的代码 ,被很多项目所普遍使用。

Testify

Testify(https://github.com/stretchr/testify)已经存在了很长时间,它几乎不需要任何介绍。但是,由于它是一个提高测试质量的有用工具和技巧的列表,它不能被遗漏。最受欢迎的功能包括简单的断言和创建模拟对象的机制,也可用于检查对它们的期望。

func TestSomething(t *testing.T) {// assert equalityassert.Equal(t, 123, 123, "they should be equal")// assert inequalityassert.NotEqual(t, 123, 456, "they should not be equal")// assert for nil (good for errors)assert.Nil(t, object)// assert for not nil (good when you expect something)if assert.NotNil(t, object) {// now we know that object isn't nil, we are safe to make// further assertions without causing any errorsassert.Equal(t, "Something", object.Value)}
}

断言的例子来自:https://github.com/stretchr/testify

// MyMockedObject is a mocked object that implements an interface
// that describes an object that the code I am testing relies on.
type MyMockedObject struct{mock.Mock
}// DoSomething is a method on MyMockedObject that implements some interface
// and just records the activity, and returns what the Mock object tells it to.
//
// In the real object, this method would do something useful, but since this
// is a mocked object - we're just going to stub it out.
//
// NOTE: This method is not being tested here, code that uses this object is.
func (m *MyMockedObject) DoSomething(number int) (bool, error) {args := m.Called(number)return args.Bool(0), args.Error(1)
}// TestSomething is an example of how to use our test object to
// make assertions about some target code we are testing.
func TestSomething(t *testing.T) {// create an instance of our test objecttestObj := new(MyMockedObject)// setup expectationstestObj.On("DoSomething", 123).Return(true, nil)// call the code we are testingtargetFuncThatDoesSomethingWithObj(testObj)// assert that the expectations were mettestObj.AssertExpectations(t)
}

模拟例子来自:https://github.com/stretchr/testify

Mockery

Mockery (https://github.com/vektra/mockery) 是一个补充工具(Golang 的模拟代码自动生成器),可以和Testify的Mock包一起使用,利用它的功能来创建模拟对象,并为项目源代码中的Golang接口自动生成模拟对象。它有助于减少很多模板式的编码任务。

// the interface declaration found in your project's source code
// Stringer接口声明
type Stringer interface {String() string
}// the generated mock file based on the interface
import "github.com/stretchr/testify/mock"type Stringer struct {mock.Mock
}func (m *Stringer) String() string {ret := m.Called()var r0 stringif rf, ok := ret.Get(0).(func() string); ok {r0 = rf()} else {r0 = ret.Get(0).(string)}return r0
}

模拟对象的例子来自:https://github.com/vektra/mockery

Gofakeit

Gofakeit(https://github.com/brianvoe/gofakeit)是一个随机数据生成器。目前,它提供了160多个函数,涵盖少数不同的主题/类别,如PersonAnimalsAddressGamesCarsBeers等等。这里的关键词是随机。随机性是计划和编写测试时需要考虑的一个重要方面。与其在测试用例中使用常量值,不如使用随机生成的值,这样我们会更有信心,即使面对未知的(随机的)输入,我们的代码仍然会以我们期望的方式表现出来。

import "github.com/brianvoe/gofakeit/v6"gofakeit.Name()             // Markus Moen
gofakeit.Email()            // alaynawuckert@kozey.biz
gofakeit.Phone()            // (570)245-7485
gofakeit.BS()               // front-end
gofakeit.BeerName()         // Duvel
gofakeit.Color()            // MediumOrchid
gofakeit.Company()          // Moen, Pagac and Wuckert
gofakeit.CreditCardNumber() // 4287271570245748
gofakeit.HackerPhrase()     // Connecting the array won't do anything, we need to generate the haptic COM driver!
gofakeit.JobTitle()         // Director
gofakeit.CurrencyShort()    // USD// randomly pick an item from a predefined list
numberList := []int{1, 36, 41, 99}
idx := gofakeit.Number(0, len(numberList)-1)
randomItem := numberList[idx]

例子来自:https://github.com/brianvoe/gofakeit

Gock

Gock(https://github.com/h2non/gock)是一个用于Golang的HTTP服务器模拟和期望库,它在很大程度上受到了NodeJs的流行和较早的同类库的启发,称为Nock[2]。与Pact-go(https://github.com/pact-foundation/pact-go)不同,它是一个轻量级的解决方案,通过http.DefaultTransport或任何http.Client使用的http.Transport拦截出站请求。

func TestSimple(t *testing.T) {defer gock.Off()gock.New("http://foo.com").Get("/bar").Reply(200).JSON(map[string]string{"foo": "bar"})res, err := http.Get("http://foo.com/bar")assert.Nil(t, err)assert.Equal(t, 200, res.StatusCode)body, _ := ioutil.ReadAll(res.Body)assert.Equal(t, `{"foo":"bar"}`, string(body)[:13])// Verify that we don't have pending mocksassert.True(t, gock.IsDone())
}

如何使用Gock的例子

Testfixtures

Testfixtures (https://github.com/go-testfixtures/testfixtures) [模仿 "Ruby on Rails方式 "为数据库应用程序编写测试](https://guides.rubyonrails.org/testing.html#the-test-database "模仿 "Ruby on Rails方式 "为数据库应用程序编写测试"),其中样本数据保存在fixtures文件中。在执行每个测试之前,测试数据库都会被清理并将fixture数据加载到数据库中。以下是我在我的项目中使用的一个例子(更多细节请看https://github.com/rafael-piovesan/go-rocket-ride)。

// Load loads database test fixtures specified by the "data" param, a list of files and/or directories (paths
// should be relative to the project's root dir).
//
// It takes as input a Postgres DSN, a list of files/directories leading to the *.yaml files and one more
// optional param, which accepts a map with data to be used while parsing files looking for template
// placeholders to be replaced.
func Load(dsn string, data []string, tplData map[string]interface{}) error {if len(data) == 0 {return errors.New("list of fixtures files/directories is empty")}db, err := sql.Open("postgres", dsn)if err != nil {return err}// find out the absolute path to this file// it'll be used to determine the project's root path_, callerPath, _, _ := runtime.Caller(0) // nolint:dogsled// look for migrations source starting from project's root dirrootPath := fmt.Sprintf("%s/../..",filepath.ToSlash(filepath.Dir(callerPath)),)// assemble a list of fixtures paths to be loadedfor i := range data {data[i] = fmt.Sprintf("%v/%v", rootPath, filepath.ToSlash(data[i]))}fixtures, err := testfixtures.New(testfixtures.Database(db),testfixtures.Dialect("postgres"),testfixtures.Template(),testfixtures.TemplateData(tplData),// Paths must come after Template() and TemplateData()testfixtures.Paths(data...),)if err != nil {return err}// load fixtures into DBreturn fixtures.Load()
}

将测试fixture加载到Postgres实例的实用程序代码

此外,当它与Testcontainers-Go以及其他迁移工具结合时,它会是一个很好的匹配,因为你可以在你的测试中以编程方式协调这些工具,就像下面这样。

- id: {{$.UserId}}email: {{$.UserEmail}}stripe_customer_id: zaZPe9XIf8Pq5NK

测试fixtures样本文件 https://gist.github.com/rafael-piovesan/2a06fca2805641d291c1f38323ef68f1#file-users-yaml

ctx := context.Background()// create database container
dsn, terminate, err := testcontainer.NewPostgresContainer()
require.NoError(t, err)
defer terminate(ctx)// migrations up
err = migrate.Up(dsn, "db/migrations")
require.NoError(t, err)// load test fixtures
userID := int64(gofakeit.Number(0, 1000))
idemKey := gofakeit.UUID()
err = testfixtures.Load(dsn, []string{"db/fixtures/users"}, map[string]interface{}{"UserId":    userID,"UserEmail": gofakeit.Email(),
})
require.NoError(t, err)

把所有东西放在一起 https://gist.githubusercontent.com/rafael-piovesan/2a06fca2805641d291c1f38323ef68f1/raw/2b481bb070ddfc6cb80d6172a0d057a36f7ca831/db_test.go

测试主函数

你有没有想过如何测试你的应用程序的主功能呢?假设您想检查代码在缺少任何环境变量的情况下的行为。假设你至少有一个简单的日志来告诉我们发生了什么,可能的方法是实际检查应用程序的输出并查找该日志。如下所示。(请参阅更多详细信息 https://github.com/rafael-piovesan/go-rocket-ride)

// main.go
package mainimport ("log"rocketride "github.com/rafael-piovesan/go-rocket-ride"
)func main() {// read config values from env varscfg, err := rocketride.LoadConfig(".")if err != nil {log.Fatalf("cannot load config: %v", err)}// continue to start your app ...
}// your tests
func TestMain(t *testing.T) {if os.Getenv("START_MAIN") == "1" {main()return}t.Run("Missing env vars", func(t *testing.T) {stdout, stderr, err := startSubprocess(t)if e, ok := err.(*exec.ExitError); ok && !e.Success() {assert.Empty(t, stdout)assert.Contains(t, stderr, "cannot load config")return}t.Fatalf("process ran with err %v, want exit status 1", err)})
}// startSubprocess calls "go test" command specifying the test target name "TestMain" and setting
// the env var "START_MAIN=1". It will cause the test to be run again, but this time calling the
// "main()" func. This way, it's possible to retrieve and inspect the app exit code along with
// the stdout and stderr as well.
// See more at: https://stackoverflow.com/a/33404435
func startSubprocess(t *testing.T, envs ...string) (stdout string, stderr string, err error) {var cout, cerr bytes.Buffer// call test suit again specifying "TestMain" as the targetcmd := exec.Command(os.Args[0], "-test.run=TestMain")// set "START_MAIN" env var along with any additional value provided as parameterenvs = append(envs, "START_MAIN=1")cmd.Env = append(os.Environ(), envs...)// capture subprocess' stdout and stderrcmd.Stdout = &coutcmd.Stderr = &cerr// run the test againerr = cmd.Run()stdout = cout.String()stderr = cerr.String()return
}

用Build标签分离测试

这个想法是按类型(即单元、集成、冒烟和端到端)或任何其他你认为合适的标准来分离测试,因为我们使用Go的内置机制,称为Build Contraints[3],也就是众所周知的Build Tags。关于构建约束最近在Go1.17的改动也可以看站长写的Go1.17 新特性:新版构建约束。

//go:build unit || usecase
// +build unit usecasepackage usecaseimport "testing"func TestUseCase(t *testing.T) {// your test code
}

然后,要选择你想运行的测试,只需调用go测试命令,指定-tags即可。

# 运行所有单元测试
go test -tags=unit ./...
# 只运行与用例相关的测试
go test -tags=usecase ./...

参考

  • https://github.com/rafael-piovesan/go-rocket-ride

  • https://go.dev/doc/code#Testing

  • https://bmuschko.com/blog/go-testing-frameworks/

  • https://github.com/orlangure/gnomock

  • https://github.com/ory/dockertest

  • https://github.com/testcontainers/testcontainers-go

  • https://github.com/stretchr/testify

  • https://github.com/vektra/mockery

  • https://github.com/brianvoe/gofakeit

  • https://github.com/h2non/gock

  • https://mickey.dev/posts/go-build-tags-testing/


欢迎关注Go招聘公众号,获取Go专题大厂内推面经简历股文等相关资料可回复和点击导航查阅。

让你如虎添翼的 Go 测试工具和技巧相关推荐

  1. 推荐几个 Go 测试工具和技巧让你在虎年如虎添翼

    今天带来一篇让你在虎年如虎添翼的Go测试工具和技巧的文章分享.大体内容翻译于 Go (Golang): Testing tools & tips to step up your game[1] ...

  2. 渗透测试 ( 5 ) --- 扫描之王 nmap、渗透测试工具实战技巧合集

    Nmap 官方文档 ( 中文文档是 Nmap 版本4.50,英文文档是最新的 ): 英文文档:https://nmap.org/book/man.html 中文文档:https://nmap.org/ ...

  3. 网站压力测试工具webbench

    webbench最多可以模拟3万个并发连接去测试网站的负载能力,个人感觉要比Apache自带的ab压力测试工具好,安装使用也特别方便. 1.适用系统:Linux 2.编译安装: 引用 wget htt ...

  4. 属性匹配工具_测试工具链——高效构建Mock服务

    现在,WEB系统的开发一般都采用前后端分离的架构,以及部分公司采用"前台-中台-后台"的组织架构,难免会出现开发进度不一致的情况,导致系统联调或测试需要等到所有依赖开发完成后才能够 ...

  5. 微软压力测试工具 web application stress

    WEB服务器的压力测试工具~ 115808 2009年8月1日 lbimba 铜牌会员 这里给广大的煤油推荐一个web网站压力测试工具.它可以用来模拟多个用户操作网站,在程序投入运行时,可以用它来进行 ...

  6. 介绍几款浏览器兼容性测试工具

    昨天和朋友聊到了有关浏览器兼容性的问题,在开发中有时的确很让人苦恼,我向他推荐了几款测试浏览器兼容的工具,分享给大伙,有什么更好的工具或是解决方法还希望大家拿出来晒一晒. IETester 这是我最先 ...

  7. python的web压力测试工具-pylot安装使用

    pylot是python编写的一款web压力测试工具.使用比较简单.而且测试结果相对稳定. 这里不得不鄙视一下apache 的ab测试,那结果真是让人蛋疼,同样的url,测试结果飘忽不定,看得人心惊肉 ...

  8. 正则表达式测试工具 Regex Tester 的使用方法

    2019独角兽企业重金招聘Python工程师标准>>> 正则表达式测试工具"RegexTester",下载地址:http://www.oschina.net/p/ ...

  9. JSON Web Tokens测试工具

    JSON Web Tokens官方提供测试工具https://jwt.io某些静态资料需要链接google.twitter服务器,被墙无法访问.现在提供可以方法测试工具http://hingtai.c ...

最新文章

  1. 机器学习误差分析(Error Analysis)实战
  2. php mysql 星级评分_jQuery+PHP星级评分实现方法_jquery
  3. MSXML解析[转]
  4. 有勇气的牛排---算法与数据
  5. OpenYurt 开源 | 云原生生态周报 Vol. 51
  6. BUG总结—— No mapping found for HTTP request with URI
  7. java六大原则_六大Java功能
  8. yum方式安装android_linux yum 命令 详解
  9. 九张 Gif 图回顾 Web 设计的 25 年历史
  10. smarty中英文多编码字符截取乱码问题
  11. 简介JavaScript的组成
  12. SDOD: Real-time Segmenting and Detecting 3D Objects by Depth(实时3D检测与分割)
  13. 音乐播放微信小程序基于node.js后台
  14. 什么是网站的统计代码
  15. 23个常见Webshell网站管理工具
  16. 惠州 菜鸟机器人_京东PK阿里谁怕谁?菜鸟称:智能机器人仓库已在广东惠阳投入使用...
  17. PMP(第六版)中的沟通方法
  18. VS2010如何安装MSComm控件
  19. static Constant expression contains invalid operat
  20. 物联网温湿度显示控制项目(网页、Android双端显示搭载linux平台网关MQTT通信)

热门文章

  1. vue-amap(高德地图)使用步骤和代码示例
  2. 针对媒体和娱乐行业的NVIDIA MAXIMUS
  3. PyPy-让Python爬的更快
  4. 深入了解重放攻击和如何防范
  5. 微信小程序前台调用讯飞语音识别接口
  6. 基于PHP+MySQL大学生心理健康管理系统的设计与实现
  7. HTML发展史及登录页面的开发
  8. linux centos yum源配置为nexus镜像源
  9. 用html和css做一个旋转的正方体
  10. windows cmd删除指定文件夹下指定时间文件