Go-kratos 框架商城微服务实战之用户服务 (二)
这篇主要编写单元测试。文章写的不清晰的地方可通过 GitHub 源码 查看, 也感谢您指出不足之处。
注:横排 … 为代码省略,为了保持文章的篇幅简洁,我会将一些不必要的代码使用横排的 . 来代替,如果你在复制本文代码块的时候,切记不要将 . 也一同复制进去。
准备工作 本文主要使用 Ginkgo 、gomock 、Gomega 工具来实现单元测试,之前不了解的同学,可以先熟悉一下相关文档。
Ginkgo 包的引入和简单介绍 1 2 $ go get github.com/onsi/gi nkgo/v2/gi nkgo $ go get github.com/onsi/g omega
第一条命令是获取 ginkgo 并且安装 ginkgo 可执行文件到 $GOPATH/bin
–- 你需要在你电脑中把 $GOPATH 配置上,并配置上它。 第二条命令安装了全部 gomega 库。这样可以导入 gomega 包到你的测试代码中:
1 2 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 官方介绍
安装 Dockertest
1 go get -u github.com /ory/dockertest/v3
编写 Dockertest 配置代码并将其用于测试,进入 service/user/internal/data/
目录新建 docker_mysql.go 文件,编写代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package data import ( "database/sql" "fmt" "github.com/ory/dockertest/v3" "log" "time" ) func DockerMysql(img, version string) (string, func()) { return innerDockerMysql(img, version ) } func innerDockerMysql(img, version string) (string, func()) { pool, err := dockertest.NewPool("" ) pool.MaxWait = time.Minute * 2 if err != nil { log .Fatalf("Could not connect to docker: %s" , err ) } 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 文件,编写代码如下:代码中有详细的注释,这里就不过多解释了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package data_testimport ( "context" "github.com/pkg/errors" "gorm.io/gorm" "testing" "user/internal/conf" "user/internal/data" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" )func TestData (t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "test biz data " ) }var cleaner func () // 定义删除 mysql 容器的回调函数var Db *data.Data var ctx context.Context func initialize (db *gorm.DB) error { err := db.AutoMigrate( &data.User{}, ) return errors.WithStack(err) }var _ = BeforeSuite(func () { 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()) })var _ = AfterSuite(func () { cleaner() })
测试模拟数据库连接,还是此目录下运行 go test
命令,得到如下结果:
注:这里可以看到虽然 0 个 Passed,但同时也是 0 个 Failed,这是因为咱们这里还没有进行测试,只是验证一下数据库是否连接成功,并未执行 CURD 之类的操作。 这里运行可能比较慢,因为它会从 docker hub 拉取 mysql 的镜像,本文使用的是 mariadb 的镜像,且我本机已经提前下载好了 mariadb:latest 镜像,如果你的电脑是苹果的 M1 处理器推荐你用 mariadb。
编写单元测试 漫长的准备工作终于完成了,接下来来正式编写单元测试的代码吧
编写 data 层的测试代码 还是 data 目录下新建 user_test.go 文件,编写内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package data_testimport ( . "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 () { ro = data.NewUserRepo(Db, nil ) uD = &biz.User{ ID: 1 , Mobile: "13803881388" , Password: "admin123456" , NickName: "aliliin" , Role: 1 , Birthday: 693629981 , } }) It("CreateUser" , func () { u, err := ro.CreateUser(ctx, uD) Ω(err).ShouldNot(HaveOccurred()) Ω(u.Mobile).Should(Equal("13803881388" )) }) })
Ω 就是 gomega 包的语法,It 是 ginkgo 包的用法。
还是此目录下运行 go test
命令,得到如下结果:
此时可以看到共有 1 个 Passed 通过。 提示: 类似 packets.go:37 : unexpected EOF 错误,是因为 docker 模拟数据库链接导致的。
引入 gomock 包,mock 对象模拟依赖项
具体的使用方法可以参考这篇 gomock 的使用文章
1 2 3 // gomock 主要包含两个部分:gomock 库和辅助代码生成工具 mockgen go get github.com/golang/m ock go get github.com/golang/m ock/gomock
编写生成 mock 文件方法 修改 user/internal/biz/user.go
文件
1 2 3 4 5 6 7 8 9 10 11 12 package biz ... // 注意这一行新增的 mock 数据的命令 //go:generate mockgen -destination=../mocks/mrepo/user.go -package=mrepo . UserRepo type UserRepo interface { CreateUser (context.Context , *User ) (*User , error) } ...
进入 biz 目录执行命令
1 mockgen -destination =../mocks/mrepo/user.go -package =mrepo . UserRepo
这里是用 gomock 提供的 mockgen 工具生成要 mock 的接口的实现,在生成 mock 代码的时候,我们用到了 mockgen 工具,这个工具是 gomock 提供的用来为要 mock 的接口生成实现的。它可以根据给定的接口,来自动生成代码。
执行完之后,你会看到多出来了 service/user/internal/mocks/mrepo/user.go
文件
编写 biz 层的测试方法 biz 层目录下,新增 biz_suite_test.go
文件,添加内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package biz_testimport ( "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.Controllervar cleaner func () var ctx context.Contextvar _ = BeforeEach(func () { ctl = gomock.NewController(GinkgoT()) cleaner = ctl.Finish ctx = context.Background() })var _ = AfterEach(func () { cleaner() })
还是 biz 层目录下,新增 user_test.go
文件,添加内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 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" )) }) })
验证单元测试 还是 biz 层目录下运行 go test
命令,得到如下结果:
结束语 到这一步 data 层测试 sql 语句的方法,biz 测试基本逻辑的方法已经编写完成并通过了测试了,service 层的单元测试大同小异,这里就不写了。
感谢您的耐心阅读,动动手指点个 star 吧。
关注我获取更新