Go:微服务架构下的单元测试(基于 Ginkgo、gomock 、Gomega)

news2024/10/5 19:13:48

文章目录

    • 简介
    • 一、Ginkgo包的引入和简单介绍
    • 二、Dockertest 使用
    • 三、编写单元测试
      • 1. 编写 data 层的测试代码
    • 四、引入 gomock 包,mock 对象模拟依赖项
      • 1. 编写生成 mock 文件方法
      • 2. 编写 biz 层的测试方法
      • 3. 验证单元测试
    • 小结


简介

本文主要使用 Ginkgo[2] 、gomock[3] 、Gomega[4] 工具来实现单元测试,之前不了解的同学,可以先熟悉一下相关文档。

一、Ginkgo包的引入和简单介绍

$ go get github.com/onsi/ginkgo/v2/ginkgo
$ go get github.com/onsi/gomega

第一条命令是获取 ginkgo 并且安装 ginkgo 可执行文件到 $GOPATH/bin
–- 你需要在你电脑中把 $GOPATH 配置上,并配置上它。第二条命令安装了全部 gomega 库。这样可以导入 gomega 包到你的测试代码中:

import "github.com/onsi/gomega"
import "github.com/onsi/ginkgo"

Ginkgo 与 Go 现有的测试基础设施挂钩,可以使用 go test 运行 Ginkgo 套件。这同时意味着 Ginkgo 测试可以和传统 Go testing 测试一起使用。go test 和 ginkgo 都会运行你套件内的所有测试。

二、Dockertest 使用

使用 Dockertest 来完成咱们服务的 Golang 链接 DB 的集成测试。Dockertest 库提供了简单易用的命令,用于启动 Docker 容器并将其用于测试。简单理解 Dockertest 工具就是 使用 docker 创建一个容器并在测试运行结束后停止并删除。具体信息请查看 Dockertest 官方介绍[5]

安装 Dockertest

go get -u github.com/ory/dockertest/v3

编写 Dockertest 配置代码并将其用于测试,进入 service/user/internal/data/, 目录新建 docker_mysql.go 文件,编写代码如下:

package data

import (
 "database/sql"
 "fmt"
 "github.com/ory/dockertest/v3" // 注意这个包的引入
 "log"
 "time"
)

func DockerMysql(img, version string) (string, func()) {
 return innerDockerMysql(img, version)
}

// 初始化 Docker mysql 容器
func innerDockerMysql(img, version string) (string, func()) {
 // uses a sensible default on windows (tcp/http) and linux/osx (socket)
 pool, err := dockertest.NewPool("")
 pool.MaxWait = time.Minute * 2
 if err != nil {
  log.Fatalf("Could not connect to docker: %s", err)
 }
 
 // pulls an image, creates a container based on it and runs it
 resource, err := pool.Run(img, version, []string{"MYSQL_ROOT_PASSWORD=secret", "MYSQL_ROOT_HOST=%"})
 if err != nil {
  log.Fatalf("Could not start resource: %s", err)
 }

 conStr := fmt.Sprintf("root:secret@(localhost:%s)/mysql?parseTime=true", resource.GetPort("3306/tcp"))

 if err := pool.Retry(func() error {
  var err error
  db, err := sql.Open("mysql", conStr)
  if err != nil {
   return err
  }
  return db.Ping()
 }); err != nil {
  log.Fatalf("Could not connect to docker: %s", err)
 }

 // 回调函数关闭容器
 return conStr, func() {
  if err = pool.Purge(resource); err != nil {
   log.Fatalf("Could not purge resource: %s", err)
  }
 }
}

使用 Ginkgo 编写链接 Dockertest 的测试代码,还是此目录下,新建 data_suite_test.go 文件,编写代码如下:代码中有详细的注释,这里就不过多解释了。

package data_test

import (
 "context"
 "github.com/pkg/errors"
 "gorm.io/gorm"
 "testing"
 "user/internal/conf"
 "user/internal/data"

 . "github.com/onsi/ginkgo"
 . "github.com/onsi/gomega"
)

// 测试 data 方法
func TestData(t *testing.T) {
 //  Ginkgo 测试通过调用 Fail(description string) 功能来表示失败
 // 使用 RegisterFailHandler 将此函数传递给 Gomega 。这是 Ginkgo 和 Gomega 之间的唯一连接点
 RegisterFailHandler(Fail)
 // 通知 Ginkgo 启动测试套件。如果您的任何 specs 失败,Ginkgo 将自动使 testing.T 失败。
 RunSpecs(t, "test biz data ")
}

var cleaner func()      // 定义删除 mysql 容器的回调函数
var Db *data.Data       // 用于测试的 data
var ctx context.Context // 上下文

// initialize  AutoMigrate gorm 自动建表的方法
func initialize(db *gorm.DB) error {
 err := db.AutoMigrate(
  &data.User{},
 )
 return errors.WithStack(err)
}

// ginkgo 使用 BeforeEach 为您的 Specs 设置状态
var _ = BeforeSuite(func() {
 // 执行测试数据库操作之前,链接之前 docker 容器创建的 mysql
 //con, f := data.DockerMysql("mysql", "latest")
 con, f := data.DockerMysql("mariadb", "latest")
 cleaner = f // 测试完成,关闭容器的回调方法
 config := &conf.Data{Database: &conf.Data_Database{Driver: "mysql", Source: con}}
 db := data.NewDB(config)
 mySQLDb, _, err := data.NewData(config, nil, db, nil)
 if err != nil {
  return
 }
 if err != nil {
  return
 }
 Db = mySQLDb
 err = initialize(db)
 if err != nil {
  return
 }
 Expect(err).NotTo(HaveOccurred())
})

// 测试结束后 通过回调函数,关闭并删除 docker 创建的容器
var _ = AfterSuite(func() {
 cleaner()
})

测试模拟数据库连接,还是此目录下运行 go test 命令,得到如下结果:
在这里插入图片描述

注:这里可以看到虽然 0 个Passed,但同时也是 0 个 Failed,这是因为咱们这里还没有进行测试,只是验证一下数据库是否连接成功,并未执行 CURD 之类的操作。这里运行可能比较慢,因为它会从docker hub 拉取 mysql 的镜像,本文使用的是 mariadb 的镜像,且我本机已经提前下载好了 mariadb:latest 镜像,如果你的电脑是苹果的M1处理器推荐你用 mariadb。

三、编写单元测试

漫长的准备工作终于完成了,接下来来正式编写单元测试的代码吧

1. 编写 data 层的测试代码

还是data目录下新建 user_test.go 文件,编写内容如下:

package data_test

import (
 . "github.com/onsi/ginkgo"
 . "github.com/onsi/gomega"
 "user/internal/biz"
 "user/internal/data"
)

var _ = Describe("User", func() {
 var ro biz.UserRepo
 var uD *biz.User
 BeforeEach(func() {
  // 这里的 Db 是 data_suite_test.go 文件里面定义的
  ro = data.NewUserRepo(Db, nil)
  // 这里你可以引入外部组装好的数据
  uD = &biz.User{
   ID:       1,
   Mobile:   "13803881388",
   Password: "admin123456",
   NickName: "aliliin",
   Role:     1,
   Birthday: 693629981,
  }
 })

 // 设置 It 块来添加单个规格
 It("CreateUser", func() {
  u, err := ro.CreateUser(ctx, uD)
  Ω(err).ShouldNot(HaveOccurred())
  // 组装的数据 mobile 为 13803881388
  Ω(u.Mobile).Should(Equal("13803881388")) // 手机号应该为创建的时候写入的手机号
 })

})

Ω 就是 gomega 包的语法,It 是 ginkgo 包的用法。

还是此目录下运行 go test 命令,得到如下结果:
在这里插入图片描述

四、引入 gomock 包,mock 对象模拟依赖项

// gomock 主要包含两个部分:gomock 库和辅助代码生成工具 mockgen
go get github.com/golang/mock  
go get github.com/golang/mock/gomock

1. 编写生成 mock 文件方法

修改 user/internal/biz/user.go 文件

package biz

// 注意这一行新增的 mock 数据的命令
//go:generate mockgen -destination=../mocks/mrepo/user.go -package=mrepo . UserRepo
type UserRepo interface {
  CreateUser(context.Context, *User) (*User, error)
}

进入 biz 目录执行命令

mockgen -destination=../mocks/mrepo/user.go -package=mrepo . UserRepo

这里是用 gomock 提供的 mockgen 工具生成要 mock 的接口的实现,在生成 mock 代码的时候,我们用到了 mockgen 工具,这个工具是 gomock 提供的用来为要mock的接口生成实现的。它可以根据给定的接口,来自动生成代码。

执行完之后,你会看到多出来 service/user/internal/mocks/mrepo/user.go 文件

2. 编写 biz 层的测试方法

biz层目录下,新增 biz_suite_test.go 文件,添加内容如下:

package biz_test

import (
 "context"
 "github.com/golang/mock/gomock"
 . "github.com/onsi/ginkgo"
 . "github.com/onsi/gomega"
 "testing"
)

func TestBiz(t *testing.T) {
 RegisterFailHandler(Fail)
 RunSpecs(t, "biz user test")
}

var ctl *gomock.Controller
var cleaner func()
var ctx context.Context

var _ = BeforeEach(func() {
 ctl = gomock.NewController(GinkgoT())
 cleaner = ctl.Finish
 ctx = context.Background()
})
var _ = AfterEach(func() {
 // remove any mocks
 cleaner()
})

还是biz层目录下,新增 user_test.go 文件,添加内容如下:

package biz_test

import (
 "github.com/golang/mock/gomock"
 . "github.com/onsi/ginkgo"
 . "github.com/onsi/gomega"
 "user/internal/biz"
 "user/internal/mocks/mrepo"
)

var _ = Describe("UserUsecase", func() {
 var userCase *biz.UserUsecase
 var mUserRepo *mrepo.MockUserRepo

 BeforeEach(func() {
  mUserRepo = mrepo.NewMockUserRepo(ctl)
  userCase = biz.NewUserUsecase(mUserRepo, nil)
 })

 It("Create", func() {
  info := &biz.User{
   ID:       1,
   Mobile:   "13803881388",
   Password: "admin123456",
   NickName: "aliliin",
   Role:     1,
   Birthday: 693629981,
  }
  mUserRepo.EXPECT().CreateUser(ctx, gomock.Any()).Return(info, nil)
  l, err := userCase.Create(ctx, info)
  Ω(err).ShouldNot(HaveOccurred())
  Ω(err).ToNot(HaveOccurred())
  Ω(l.ID).To(Equal(int64(1)))
  Ω(l.Mobile).To(Equal("13803881388"))
 })

})

3. 验证单元测试

还是 biz 层目录下运行 go test 命令,得到如下结果:
在这里插入图片描述

小结

到这一步 data 层测试 sql 语句的方法,biz 测试基本逻辑的方法已经编写完成并通过了测试了,service 层的单元测试大同小异,这里就不写了。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue之watch监听

计算属性不能用异步代码;有返回值的函数,用异步代码也不行;没有返回值的函数,可以用异步代码;watch监听也可以放异步代码;data、methods、computed、watch的区别: data:状态&#xf…

试题 算法提高 网格贪吃蛇(离散化 + DP)

资源限制 内存限制:256.0MB C/C时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述   那个曾经风靡全球的贪吃蛇游戏又回来啦!这次贪吃蛇在m行n列的网格上沿格线爬行,从左下角坐标为(0,0)的格…

miui14即将闪亮登场 小米首批机型搭配名单 旧版系列机型可能无望更新

小米系列机型年底大动作。新版机型发布和miui14系统将会在一些机型中更新,而最新的miui14版本以简便 精简 轻巧为目的。希望miui14版本能为米粉带来意想不到的期待。以往较多米粉都反馈系统越更新越臃肿。系统资源占用太多。 小米 MIUI 14 的版本号以及适配支持机型…

柯桥俄语培训机构哪家好,能说出“как”的多少种用法呢?

学了这么久俄语的你,能说出“как”的多少种用法呢? 一起来看看这份“как”最强使用攻略吧~ 01 作为疑问副词在句中做状语,"如何""怎么样""多么" 例: Как твои дела? …

H3C 防火墙混合实验

H3C 防火墙混合实验一、项目拓扑二、项目需求三、配置步骤1. 配置IP地址2. FW默认路由,FW指向internet3. 绑定防火墙安全域端口4. 配置防火墙安全策略5. OSPF6. NAT7. PC 默认路由,PC 指向 R1链接: 拓扑下载 一、项目拓扑 二、项目需求 根据题目要求配…

6种常见三维曲面的Three.js实现

这篇文章详细介绍了三次曲线、贝塞尔曲线和 B 样条曲线和曲面背后的数学原理,并提供了使用 Three.js 库实现的代码。 1、简介 你可以在这里访问上图应用程序。此应用程序名为 CurSur,是 Curves and Surfaces 的简称。原始代码可以在这里获取。 在几何设…

算法训练Day27 | LeetCode39. 组合总和 (元素可重复,数组长度不限);40. 组合总和III(去重);131.分割回文串

目录 LeetCode39. 组合总和 1. 思路 2. 代码实现 3. 剪枝优化 4. 复杂度分析 5. 思考与收获 LeetCode40. 组合总和III 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考与收获 LeetCode131.分割回文串 1. 思路 2. 代码实现 3. 复杂度分析 4. 思考与收获 LeetCode39…

通过DewarpNet解决图片扭曲问题

一、论文 DewarpNet:使用堆叠的三维和二维回归网络进行单幅图像文件纠正 论文地址: https://paperswithcode.com/paper/dewarpnet-single-image-document-unwarping 代码地址: https://github.com/cvlab-stonybrook/DewarpNet 二、效果展示 2.1 论文图片效果展示 从上往下…

Android Material Design之SwitchMaterial(三)

老规矩先上图 引入 implementation com.google.android.material:material:1.4.0说明 该控件就是Switch控件的增强版本,属性基本一致 属性 属性描述android:text文本android:textOn滑块打开时显示的文本android:textOff滑块关闭时显示的文本android:thumb滑块图片app:trac…

这款比奥维地图还方便的地图软件!轻便好用,功能很强大

写方案或报告的人往往会遇到需要在地图上标注的问题。最简单的方法莫过于在百度上截图,然后在ppt里标注。现场勘察需要定位,最基本的方法是利用手机读取和记录经纬度坐标。但使用百度底图标注给客户的感觉太普通,而勘察现场记录的经纬度数据事…

深度分页、唯一索引的坑、分库分表、查询分离、连接池、bufferpool优化等

文章目录表过大深度分页count(*) 与 count(列名)唯一索引分库分表只分库不分表不分库只分表分库也分表查询分离使用方法查询分离的适用场景冷热分离适用场景实现方案数据库连接池优化主键无序buffer pool 太小MySQL频繁抖动的性能优化原因解决表过大 历史数据进行归档 深度分页…

需求开发到一半需要改别的分支的bug该怎么办呢?(git stash 和 git commit)

在实际开发中,经常我们会遇到需求开发到一半,别的分支有bug急需解决的情况,这个时候我们改怎么办呢? 有的人会说可以先提交当前分支的代码再切换到别的分支改bug,当然这样是没问题的,但是呢,在项…

【计算机毕业设计】1.房屋租赁系统

一、系统截图(需要演示视频可以私聊) 摘要 当今社会房屋租赁、出售买卖是必不可少的,人们不管走到哪里都需要有一个温馨的家,有一个落脚之地,所以房屋租赁、出售市场也是非常火爆!不管是房屋租赁、出售、中…

基于conda的OpenCV库安装

基于conda的OpenCV库安装 OpenCV库的调用名是cv2 所以会看到这样的import语句 import cv2这句话就是对openCV库的调用 openCV库的下载安装 First 好像不需要像网上的教程那样, 首先去官网下载exe执行文件, 然后在VS中进行配置 直接在teminal中, 进入要指定的conda环境, …

编译原理实验--实验二 递归下降法判断算术表达式的正确性--Python实现

目录 一、实验目的和要求 二、实验内容 三、实验环境 四、实验步骤 1、语法分析所依据的文法; 2、给出消除左递归及提取左公因子的文法; 五、测试要求 六、实验步骤 1、语法分析所依据的文法 2、给出消除左递归及提取左公因子的文法&#xff1…

iNFTnews|国内数藏平台大撤退,寒冬之下海外市场是否有出路?

腾讯旗下继腾讯新闻、幻核之后,仅存的数藏平台也关停了。 11月16日,据界面新闻报道,腾讯TME旗下QQ音乐已经叫停“TME数字藏品”业务,原团队部分成员已内部活水。 一接近腾讯集团的知情人士称,腾讯方面曾对数字藏品业…

【Linux】生产者消费者模型

文章目录1.生产者消费者模型1.1生产者消费者模型的特点1.2生产者消费者模型的原则1.3生产者消费者模型的优点2.基于阻塞队列的生产者消费者模型2.1如何理解生产者消费者模型的并发?3.信号量3.1信号量接口3.2基于环形队列的生产者消费者模型3.3信号量和条件变量的区别…

Git_GitHub——基本操作、创建远程库、远程库操作、团队协作、SSH免密登录

网址:GitHub: Let’s build from here GitHub 目录 一、创建远程仓库 二、远程库操作 2.1 查看远程库别名 2.2 创建远程仓库别名 2.3 推送本地分支到远程仓库 2.4 拉取远程库到本地库 2.5 克隆远程库到本地 三、 跨团队协作 3.1 团队内协作 3.2 跨团队协作 四、SSH免密码登…

Go : golang发布三方包流程简介

文章目录一、创建项目仓库二、拉去仓库,编辑代码三、推送与发布代码四、使用发布的第三方包小结一、创建项目仓库 1.输入仓库的名字,我这里输入simpleExample,用来做演示 2.选择public,公开。要不并不好拉 3.选择需要添加的文件(…

用PyPy加速Python程序

用PyPy加速Python程序 在《Python性能优化指南–让你的Python代码快x3倍的秘诀》中有提到,我们可以用更好的Python运行环境或运行时优化来提升Python的速度,其中最成熟、使用最简单的当属PyPy。用PyPy,可以在不改变源代码的情况下&#xff…