Golang常用Mock方式總結

      網友投稿 2873 2025-04-01

      1.通用Mock方式


      類似Java Mockito。testify和mockery結合使用,testify是一個golang測試框架,主要有assert、mock和test suite三個特性,mockery利用testify的mock來生成mock的代碼。

      testify包下載: go get github.com/stretchr/testify mockery安裝: go get github.com/vektra/mockery/.../

      mockery會根據定義的interface生成對應的mock struct。

      示例代碼

      common/etcd/client.go

      common/etcd/mocks/EtcdClient.go

      sql-driver/rds/config/loader/remote_configuration_loader_test.go

      1. 生成mock strcut

      命令行執行go generate 或者使用goland直接生成,此處會自動創建mocks目錄,以及對應的mock struct文件。

      生成的代碼如下所示:

      // Code generated by mockery v1.0.0. DO NOT EDIT. package mocks import ( etcd "github.com/huaweicloud/devcloud-go/common/etcd" mock "github.com/stretchr/testify/mock" clientv3 "go.etcd.io/etcd/client/v3" ) // EtcdClient is an autogenerated mock type for the EtcdClient type type EtcdClient struct { mock.Mock } // Close provides a mock function with given fields: func (_m *EtcdClient) Close() error { ret := _m.Called() var r0 error if rf, ok := ret.Get(0).(func() error); ok { r0 = rf() } else { r0 = ret.Error(0) } return r0 } // Del provides a mock function with given fields: key func (_m *EtcdClient) Del(key string) (int64, error) { ret := _m.Called(key) var r0 int64 if rf, ok := ret.Get(0).(func(string) int64); ok { r0 = rf(key) } else { r0 = ret.Get(0).(int64) } var r1 error if rf, ok := ret.Get(1).(func(string) error); ok { r1 = rf(key) } else { r1 = ret.Error(1) } return r0, r1 } // Get provides a mock function with given fields: key func (_m *EtcdClient) Get(key string) (string, error) { ret := _m.Called(key) var r0 string if rf, ok := ret.Get(0).(func(string) string); ok { r0 = rf(key) } else { r0 = ret.Get(0).(string) } var r1 error if rf, ok := ret.Get(1).(func(string) error); ok { r1 = rf(key) } else { r1 = ret.Error(1) } return r0, r1 } // List provides a mock function with given fields: prefix func (_m *EtcdClient) List(prefix string) ([]*etcd.KeyValue, error) { ret := _m.Called(prefix) var r0 []*etcd.KeyValue if rf, ok := ret.Get(0).(func(string) []*etcd.KeyValue); ok { r0 = rf(prefix) } else { if ret.Get(0) != nil { r0 = ret.Get(0).([]*etcd.KeyValue) } } var r1 error if rf, ok := ret.Get(1).(func(string) error); ok { r1 = rf(prefix) } else { r1 = ret.Error(1) } return r0, r1 } // Put provides a mock function with given fields: key, value func (_m *EtcdClient) Put(key string, value string) (string, error) { ret := _m.Called(key, value) var r0 string if rf, ok := ret.Get(0).(func(string, string) string); ok { r0 = rf(key, value) } else { r0 = ret.Get(0).(string) } var r1 error if rf, ok := ret.Get(1).(func(string, string) error); ok { r1 = rf(key, value) } else { r1 = ret.Error(1) } return r0, r1 } // Watch provides a mock function with given fields: prefix, startIndex, onEvent func (_m *EtcdClient) Watch(prefix string, startIndex int64, onEvent func(*clientv3.Event)) { _m.Called(prefix, startIndex, onEvent) }

      2. 業務代碼調用EtcdClient

      此處定義了一個RemoteConfigurationLoader,其成員etcdClient的類型為上述定義的EtcdClient interface,在Get方法中調用EtcdClient的Get方法。

      type RemoteConfigurationLoader struct { etcdClient etcd.EtcdClient dataSourceKey string routerKey string activeKey string listeners []config.RouterConfigurationListener } func (l *RemoteConfigurationLoader) Get()(string, string, string) { dataSourceConfig, err := l.etcdClient.Get(l.dataSourceKey) if err != nil { return "", "", "" } routerConfig, err := l.etcdClient.Get(l.routerKey) if err != nil { log.Printf("ERROR: get remote routerConfig failed, %v", err) return "", "", "" } active, err := l.etcdClient.Get(l.activeKey) if err != nil { log.Printf("ERROR: get remote active failed, %v", err) return "", "", "" } return dataSourceConfig, routerConfig, active }

      3. 測試代碼編寫

      編寫對RemoteConfigurationLoader的Get()方法的測試代碼,對EtcdClient的Get方法進行mock。

      1 import ( 2 "fmt" 3 "testing" 4 5 "github.com/huaweicloud/devcloud-go/common/etcd/mocks" 6 "github.com/stretchr/testify/assert" 7 ) 8 9 func TestRemoteConfigurationLoader_Get(t *testing.T) { 10 mockClient := &mocks.EtcdClient{} 11 loader := &RemoteConfigurationLoader{ 12 dataSourceKey: "datasourceKey", 13 routerKey: "routerKey", 14 activeKey: "activeKey", 15 etcdClient: mockClient, 16 } 17 mockClient.On("Get", loader.dataSourceKey).Return("data", nil).Once() 18 mockClient.On("Get", loader.routerKey).Return("router", nil).Once() 19 mockClient.On("Get", loader.activeKey).Return("active", nil).Once() 20 datasource, router, active := loader.Get() 21 assert.Equal(t, "data", datasource) 22 assert.Equal(t, "router", router) 23 assert.Equal(t, "active", active) 24 }

      其中17-19行代碼就是在mock我們想要的數據。mockClient調用On方法,首先傳入要mock的方法名字,然后傳入方法參數,此處是利用golang的反射來實現的。Return方法中傳入想要mock的返回數據,最后調用Once()方法表示此方法只執行一次。

      4. 參考文檔

      https://segmentfault.com/a/1190000016897506

      https://www.xuanzhangjiong.top/2019/10/12/mockery%E4%BB%8B%E7%BB%8D%E5%8F%8A%E4%BD%BF%E7%94%A8/

      https://github.com/vektra/mockery

      2. Mysql Mock

      2.1 mock mySQL Server(推薦)

      利用 github.com/dolthub/go-mysql-server,go-mysql-server基于Mysql語法,解析標準sql,它可以在內存中啟動一個mySQL Server

      安裝:

      go get github.com/dolthub/go-mysql-server

      示例代碼:

      package main import ( "fmt" "time" sqle "github.com/dolthub/go-mysql-server" "github.com/dolthub/go-mysql-server/auth" "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/server" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/information_schema" ) const ( user = "user" passwd = "pass" address = "localhost" port = "13306" dbName = "test" tableName = "pets" ) func main() { engine := sqle.NewDefault( sql.NewDatabaseProvider( createTestDatabase(), information_schema.NewInformationSchemaDatabase(), )) config := server.Config{ Protocol: "tcp", Address: fmt.Sprintf("%s:%s", address, port), Auth: auth.NewNativeSingle(user, passwd, auth.AllPermissions), } s, err := server.NewDefaultServer(config, engine) if err != nil { panic(err) } go func() { s.Start() }() fmt.Println("mysql-server started!") <- make(chan interface{}) } func createTestDatabase() *memory.Database { db := memory.NewDatabase(dbName) table := memory.NewTable(tableName, sql.Schema{ {Name: "name", Type: sql.Text, Nullable: false, Source: tableName}, {Name: "email", Type: sql.Text, Nullable: false, Source: tableName}, {Name: "phone_numbers", Type: sql.JSON, Nullable: false, Source: tableName}, {Name: "created_at", Type: sql.Timestamp, Nullable: false, Source: tableName}, }) db.AddTable(tableName, table) ctx := sql.NewEmptyContext() rows := []sql.Row{ sql.NewRow("John Doe", "jasonkay@doe.com", []string{"555-555-555"}, time.Now()), sql.NewRow("John Doe", "johnalt@doe.com", []string{}, time.Now()), sql.NewRow("Jane Doe", "jane@doe.com", []string{}, time.Now()), sql.NewRow("Evil Bob", "jasonkay@gmail.com", []string{"555-666-555", "666-666-666"}, time.Now()), } for _, row := range rows { _ = table.Insert(ctx, row) } return db

      2.2 mock sql driver

      使用 DATA-DOG/go-sqlmock,該包實現了go sdk sql/driver的接口,本質上是一個mock驅動

      安裝:

      go get github.com/DATA-DOG/go-sqlmock

      示例代碼

      原生sql代碼使用 示例代碼見:https://github.com/DATA-DOG/go-sqlmock

      編寫ut時利用定義的globalMock做dml前置操作,具體使用方法見官方文檔。

      var ( globalOrm orm.Ormer once sync.Once mockOnce sync.Once globalMockOrm orm.Ormer GlobalMock sqlmock.Sqlmock ) func GetOrmer() orm.Ormer { if utils.GetenvOrDefault("isTest", "") == "true" { mockOnce.Do(func() { var db *sql.DB db, GlobalMock, _ = sqlmock.New() GlobalMock.ExpectPrepare("SELECT TIMEDIFF") GlobalMock.ExpectPrepare("SELECT ENGINE") globalMockOrm, _ = orm.NewOrmWithDB("mysql", "default", db) }) return globalMockOrm } once.Do(func() { // override the default value(1000) to return all records when setting no limit orm.DefaultRowsLimit = -1 globalOrm = orm.NewOrm() }) return globalOrm }

      測試代碼

      type Book struct { Id int64 `gorm:"column:id"` Title string `gorm:"column:title"` } func TestSqlMockBeegoOrm(t *testing.T) { os.Setenv("isTest", "true") ormer := driver_test.GetOrmer() GlobalMock.ExpectQuery("SELECT").WillReturnRows( sqlmock.NewRows([]string{"id", "title"}). AddRow(1, "one")) book := &Book{Id:1} err := ormer.Read(book) assert.Nil(t, err) assert.Equal(t, "one", book.Title) }

      Golang常用Mock方式總結

      var ( globalGormDB *gorm.DB globalMockGormDB *gorm.DB globalMock sqlmock.Sqlmock once sync.Once mockOnce sync.Once ) func GetGormDB() *gorm.DB { if utils.GetenvOrDefault("isTest", "") == "true" { mockOnce.Do(func() { var db *sql.DB db, globalMock, _ = sqlmock.New() globalMockGormDB, _ = gorm.Open(mysql.New(mysql.Config{ Conn: db, SkipInitializeWithVersion: true, }), &gorm.Config{}) }) return globalMockGormDB } once.Do(func() { globalGormDB, _ = gorm.Open(mysql.Open("user:pass@tcp(127.0.0.1:3306)/dbname"), &gorm.Config{}) }) return globalGormDB }

      測試代碼

      func TestSqlMockGorm(t *testing.T) { os.Setenv("isTest", "true") gormDB := driver_test.GetGormDB() driver_test.GlobalMock.ExpectQuery(regexp.QuoteMeta("SELECT * FROM `books`")).WillReturnRows( sqlmock.NewRows([]string{"id", "title"}). AddRow(1, "one"). AddRow(2, "two")) var books []Book err := gormDB.Find(&books).Error assert.Nil(t, err) assert.Equal(t, 2, len(books)) assert.Equal(t, int64(1), books[0].Id) assert.Equal(t, "one", books[0].Title) assert.Equal(t, int64(2), books[1].Id) assert.Equal(t, "two", books[1].Title) }

      2.3 使用優劣

      個人感覺mock mysql server最方便,對代碼侵入較少,測試代碼也會更少,測試范圍會更廣。

      mock sql driver只適合對業務層mock數據庫操作,測試業務代碼;當使用復雜sql時,需要將orm的鏈式操作等轉為一個復雜sql語句用于mock,需要編寫大量測試代碼。

      而mock mysql server在此基礎上還能測試數據庫操作的代碼是否正確。

      2.4 參考文檔

      使用純Go實現的Mysql數據庫

      https://pkg.go.dev/github.com/dolthub/go-mysql-server#section-readme

      https://zhuanlan.zhihu.com/p/249313716

      https://github.com/DATA-DOG/go-sqlmock

      https://blog.csdn.net/weixin_44294408/article/details/120698482

      3. Redis Mock

      使用github.com/alicebob/miniredis/v2 ,miniredis可以在內存中啟動一個redis server,支持大部分redis命令,具體支持情況見github readme

      示例代碼:

      redis/devspore_client_test.go

      import ( "context" "testing" "github.com/alicebob/miniredis/v2" "github.com/go-redis/redis/v8" "github.com/stretchr/testify/assert" ) func TestMockRedis(t *testing.T) { server, _ := miniredis.Run() client := redis.NewClient(&redis.Options{Addr: server.Addr()}) ctx := context.Background() client.Set(ctx, "test", "val", 0) res := client.Get(ctx, "test") assert.Nil(t, res.Err()) assert.Equal(t, "val", res.Val()) server.Close() }

      4. Ginkgo測試框架

      Ginkgo是一個BDD(Behavior Driven Development)風格的go測試框架,與Gomega配合使用,在需要寫大量單測時,特別是需要一些通用代碼時,Ginkgo可以使用BeforeEach和AfterEach將每個用例的通用步驟提取出來,會讓代碼看起來很清爽。

      具體使用方法見 https://ke-chain.github.io/ginkgodoc/

      示例代碼

      devcloud-go/sql-driver/mysql/devspore_driver_test.go 使用Ginkgo框架編寫driver的CRUD測試代碼,結合mock mysql server使用。

      import ( "database/sql" "fmt" "testing" "github.com/huaweicloud/devcloud-go/sql-driver/rds/config" "github.com/huaweicloud/devcloud-go/sql-driver/rds/datasource" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" sqle "github.com/dolthub/go-mysql-server" "github.com/dolthub/go-mysql-server/auth" "github.com/dolthub/go-mysql-server/memory" "github.com/dolthub/go-mysql-server/server" mocksql "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/go-mysql-server/sql/information_schema" ) func TestGinkgoSuite(t *testing.T) { RegisterFailHandler(Fail) RunSpecs(t, "mysql") } var _ = Describe("CRUD", func() { var ( devsporeDB *sql.DB masterDB *sql.DB err error activeNode *datasource.NodeDataSource ) go startMockServer() BeforeEach(func() { devsporeDB, err = sql.Open("devspore_mysql", "../rds/resources/driver_test_config.yaml") Expect(err).NotTo(HaveOccurred()) activeNode, err = initDB() Expect(err).NotTo(HaveOccurred()) masterDB, err = sql.Open("mysql", activeNode.MasterDataSource.Dsn) Expect(err).NotTo(HaveOccurred()) }) AfterEach(func() { Expect(devsporeDB.Close()).NotTo(HaveOccurred()) Expect(masterDB.Close()).NotTo(HaveOccurred()) }) It("Test Query", func() { var ( val string flag bool ) err = devsporeDB.QueryRow("SELECT val FROM foo WHERE id=?", id1).Scan(&val) Expect(err).NotTo(HaveOccurred()) for _, slave := range activeNode.SlavesDatasource { if slave.Name == val { flag = true } } Expect(flag).To(Equal(true)) }) It("Test Insert", func() { var val string _, err = devsporeDB.Exec(`INSERT INTO foo (id, val) VALUES (?, ?)`, id2, "insert") Expect(err).NotTo(HaveOccurred()) err = masterDB.QueryRow("SELECT val FROM foo WHERE id=?", id2).Scan(&val) Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("insert")) }) It("Test Update", func() { var val string _, err = devsporeDB.Exec(`UPDATE foo set val=? where id=?`, "update", id1) Expect(err).NotTo(HaveOccurred()) err = masterDB.QueryRow("SELECT val FROM foo WHERE id=?", id1).Scan(&val) Expect(err).NotTo(HaveOccurred()) Expect(val).To(Equal("update")) }) It("Test Delete", func() { var val string _, err = devsporeDB.Exec(`DELETE FROM foo where id=?`, id1) Expect(err).NotTo(HaveOccurred()) err = masterDB.QueryRow("SELECT val FROM foo WHERE id=?", id1).Scan(&val) Expect(err).To(HaveOccurred()) Expect(err.Error()).To(Equal("sql: no rows in result set")) }) }) const ( user = "root" passwd = "root" address = "localhost" port = "13306" ) func startMockServer() { engine := sqle.NewDefault( mocksql.NewDatabaseProvider( memory.NewDatabase("ds0"), memory.NewDatabase("ds1"), memory.NewDatabase("ds0-slave0"), memory.NewDatabase("ds0-slave1"), memory.NewDatabase("ds1-slave0"), memory.NewDatabase("ds1-slave1"), information_schema.NewInformationSchemaDatabase(), )) config := server.Config{ Protocol: "tcp", Address: fmt.Sprintf("%s:%s", address, port), Auth: auth.NewNativeSingle(user, passwd, auth.AllPermissions), } s, err := server.NewDefaultServer(config, engine) if err != nil { panic(err) } go func() { s.Start() }() fmt.Println("mysql-server started!") }

      5. 代碼參考

      示例代碼標有文件路徑的均來自devcloud-go項目,具體可看https://github.com/huaweicloud/devcloud-go。本文是在本人開發devcloud-go過程中積累而成,各位看官可以移步devcloud-go項目點個star~

      Go MySQL Redis 單元測試

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。

      上一篇:excel表格勾的符號在哪里怎么輸入(excel 打勾的符號怎么輸入)
      下一篇:生產工序管理系統(生產工序管理系統有哪些)
      相關文章
      亚洲AV无码日韩AV无码导航| 亚洲婷婷天堂在线综合| 亚洲国产精品无码久久一线| 337p日本欧洲亚洲大胆人人| 亚洲中文字幕久久精品无码A| 亚洲avav天堂av在线网爱情| 亚洲免费在线视频播放| 亚洲视频国产精品| 97se亚洲综合在线| 亚洲国产成人久久三区| 亚洲欧洲国产视频| 亚洲伊人久久大香线焦| 亚洲人成图片网站| 国产精品亚洲四区在线观看| 久久综合久久综合亚洲| 亚洲精华液一二三产区| 日韩色日韩视频亚洲网站| 亚洲第一视频在线观看免费| 亚洲免费在线观看| 亚洲色偷偷综合亚洲AVYP| 精品亚洲永久免费精品| 亚洲国产精品久久久久| 亚洲AV无码欧洲AV无码网站| 亚洲精品人成在线观看| 亚洲欧洲尹人香蕉综合| 亚洲一区二区三区精品视频 | 亚洲精品蜜夜内射| 亚洲一本到无码av中文字幕| 亚洲av成人一区二区三区观看在线| 国产成人亚洲综合在线| 综合久久久久久中文字幕亚洲国产国产综合一区首 | 亚洲偷自精品三十六区| 亚洲人成色4444在线观看| 国产综合激情在线亚洲第一页| 亚洲精品人成无码中文毛片 | 亚洲中文无码卡通动漫野外| 色九月亚洲综合网| 亚洲最大AV网站在线观看| 亚洲人成网站在线播放影院在线| 亚洲国产综合精品| 亚洲日韩国产一区二区三区在线|