go 单元测试 testing 打印输出_2020,你需掌握go 单元测试进阶篇
本文說明go語言自帶的測試框架未提供或者未方便地提供的測試方案,主要是用于解決寫單元測試中比較頭痛的依賴問題。也就是偽造模式,經(jīng)典的偽造模式有樁對象(stub),模擬對象(mock)和偽對象(fake)。比較幸運的是,社區(qū)有豐富的第三方測試框架支持支持。下面就對筆者親身試用并實踐到項目中的幾個框架做介紹:
1.gomock
gomock模擬對象的方式是讓用戶聲明一個接口,然后使用gomock提供的mockgen工具生成mock對象代碼。要模擬(mock)被測試代碼的依賴對象時候,即可使用mock出來的對象來模擬和記錄依賴對象的各種行為:比如最常用的返回值,調(diào)用次數(shù)等等。文字敘述有點抽象,直接上代碼:
dick.go中DickFunc依賴外部對象OutterObj,本示例就是說明如何使用gomock框架控制所依賴的對象。
func DickFunc( outterObj MockInterface,para int)(result int){fmt.Println("This init DickFunc")fmt.Println("call outter.func:")return outterObj.OutterFunc(para) }mockgen工具命令是:
mockgen -source {source_file}.go -destination {dest_file}.go
比如,本示例即是:
mockgen -source src_mock.go -destination dst_mock.go
執(zhí)行完后,可在同目錄下找到生成的dst_mock.go文件,可以看到mockgen工具也實現(xiàn)了接口:
接下來就可以使用mockgen工具生成的NewMockInterFace來生產(chǎn)mock對象,使用這個mock對象。OutterFunc()這個函數(shù),gomock在控制mock類時支持鏈式編程的方式,其原理和其他鏈式編程類似一直維持了一個Call對象,把需要控制的方法名,入?yún)?#xff0c;出參,調(diào)用次數(shù)以及前置和后置動作等,最后使用反射來調(diào)用方法,所以這個Call對象是mock對象的代理。jmockit的早期版本也是jdk自帶的java.reflect.Proxy動態(tài)代理實現(xiàn)的(最近的版本是動態(tài)Instrumentation配合代理模式)。
在本示例中只簡單的更改了返回值,拋磚引玉:
func TestDickFunc(t *testing.T ){mockCtrl := gomock.NewController(t) //defer mockCtrl.Finish()mockObj := dick.NewMockMockInterface(mockCtrl)mockObj.EXPECT().OutterFunc(3).Return(10)result :=dick.DickFunc(mockObj,3)t.Log("resutl:",result)}使用go test命令執(zhí)行這個單測
從結(jié)果看:本來應該輸出3,最后輸出就是10,和其他語言mock框架相似,生產(chǎn)出來的Mock對象不用自己去重定義這么麻煩。
2.httpexcept
由于go在網(wǎng)絡架構(gòu)上的優(yōu)秀封裝,使得go在很多網(wǎng)絡場景被廣泛使用,而http協(xié)議是其中重要部分,在面對http請求的時候,可以對http的client進行測試,算是mock的特殊應用場景。
看一個簡單的示例就輕松的看懂了:
func TestHttp(t *testing.T) {handler := FruitServer()server := httptest.NewServer(handler)defer server.Close()e := httpexpect.New(t, server.URL)e.GET("/fruits").Expect().Status(http.StatusOK).JSON().Array().Empty() }其中還支持對不同方法(包括Header,Post等)的構(gòu)造以及返回值Json的自定義
3.testify
還有一個testify使用起來可以說兼容了《一》中的gocheck和gomock,但是其mock使用稍微有點煩雜,使用繼承tetify.Mock(匿名組合)重新實現(xiàn)需要Mock的接口,在這個接口里使用者自己使用Called(反射實現(xiàn))被Mock的接口。
《單元測試的藝術(shù)》中認為stub和mock最大的區(qū)別就依賴對象是否和被測對象有交互,而從結(jié)果看就是樁對象不會使測試失敗,它只是為被測對象提供依賴的對象,并不改變測試結(jié)果,而mock則會根據(jù)不同的交互測試要求,很可能會更改測試的結(jié)果。說了這么多理論,但其實這兩種方法都不是割裂的,所以gomock框架除了像其名字一樣可以模擬對象以外,還提供了樁對象的功能(stub)。以其實現(xiàn)來說,更像是一個樁對象的注入。但是因為兼容了多個有用的功能,所以其在社區(qū)最為火爆。
4.go-sqlmock
還有一種比較常見的場景就是和數(shù)據(jù)庫的交互場景,go-sqlmock是sql模擬(Mock)驅(qū)動器,主要用于測試數(shù)據(jù)庫的交互,go-sqlmock提供了完整的事務的執(zhí)行測試框架,最新的版本(16.11.02)還支持prepare參數(shù)化提交和執(zhí)行的Mock方案。
比如有這樣的被測函數(shù):
func recordStats(db *sql.DB, userID, productID int64) (err error) {tx, err := db.Begin()if err != nil {return}defer func() {switch err {case nil:err = tx.Commit()default:tx.Rollback()}}()if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {return}if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil {return}return }func main() {db, err := sql.Open("mysql", "root@/root")if err != nil {panic(err)}defer db.Close()if err = recordStats(db, 1 , 5 ); err != nil {panic(err)} }單測時:
func TestShouldUpdateStats(t *testing.T) {db, mock, err := sqlmock.New()if err != nil {t.Fatalf("mock error: '%s' ", err)}defer db.Close()mock.ExpectBegin()mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectCommit()if err = recordStats(db, 2, 3); err != nil {t.Errorf("exe error: %s", err)}if err := mock.ExpectationsWereMet(); err != nil {t.Errorf("not implements: %s", err)} }//測試回滾 func TestShouldRollbackStatUpdatesOnFailure(t *testing.T) {db, mock, err := sqlmock.New()if err != nil {t.Fatalf("mock error: '%s'", err)}defer db.Close()mock.ExpectBegin()mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnError(fmt.Errorf("some error"))mock.ExpectRollback()// 執(zhí)行被測方法,有錯if err = recordStats(db, 2, 3); err == nil {t.Errorf("not error")}// 執(zhí)行被測方法,mock對象if err := mock.ExpectationsWereMet(); err != nil {t.Errorf("not implements: %s", err)} }介紹了這么多框架,最后需要說明的也可能最重要的是寫代碼時就應該考慮代碼是可被測試的。要使得單元測試容易寫,或者說代碼容易被測,其實很重要的一個部分就是被測代碼本身是容易被測的,也就是說在設計和編寫代碼的時候就應該先想到相好如何單元測試,甚至有人提出可以先寫單元測試,再寫具體被測代碼。因為一個接口(或者稱為單元)在被設計好后,它實現(xiàn)就確定了,實際效果也確定了。這種方式被稱作測試驅(qū)動開發(fā)(Test-Driven Development, TDD)。而對于已經(jīng)寫好的代碼,很大程度上不好測試,有一種方式是測試性重構(gòu),就是為了更好的測試而進行重構(gòu)。這些一定程度上來說比了解這些框架更重要。
以上內(nèi)容就是本篇的全部內(nèi)容以上內(nèi)容希望對你有幫助,有被幫助到的朋友歡迎點贊,評論。
如果對軟件測試、接口測試、自動化測試、面試經(jīng)驗交流。感興趣可以關(guān)注我,我們會有同行一起技術(shù)交流哦。
總結(jié)
以上是生活随笔為你收集整理的go 单元测试 testing 打印输出_2020,你需掌握go 单元测试进阶篇的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: expdp导出表结构_(转)oracle
- 下一篇: android log.d 参数,And