2020就缆,你需掌握go 單元測試進(jìn)階篇

本文說明go語言自帶的測試框架未提供或者未方便地提供的測試方案帖渠,主要是用于解決寫單元測試中比較頭痛的依賴問題。也就是偽造模式竭宰,經(jīng)典的偽造模式有樁對象(stub),模擬對象(mock)和偽對象(fake)空郊。比較幸運的是,社區(qū)有豐富的第三方測試框架支持支持切揭。下面就對筆者親身試用并實踐到項目中的幾個框架做介紹:

1.gomock

gomock模擬對象的方式是讓用戶聲明一個接口狞甚,然后使用gomock提供的mockgen工具生成mock對象代碼。要模擬(mock)被測試代碼的依賴對象時候廓旬,即可使用mock出來的對象來模擬和記錄依賴對象的各種行為:比如最常用的返回值哼审,調(diào)用次數(shù)等等。文字?jǐn)⑹鲇悬c抽象嗤谚,直接上代碼:

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類時支持鏈?zhǔn)骄幊痰姆绞剑湓砗推渌準(zhǔn)骄幊填愃埔恢本S持了一個Call對象离福,把需要控制的方法名杖狼,入?yún)ⅲ鰠⒀{(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é)果看:本來應(yīng)該輸出3次舌,最后輸出就是10熄攘,和其他語言mock框架相似,生產(chǎn)出來的Mock對象不用自己去重定義這么麻煩彼念。

2.httpexcept

由于go在網(wǎng)絡(luò)架構(gòu)上的優(yōu)秀封裝挪圾,使得go在很多網(wǎng)絡(luò)場景被廣泛使用,而http協(xié)議是其中重要部分逐沙,在面對http請求的時候哲思,可以對http的client進(jìn)行測試,算是mock的特殊應(yīng)用場景酱吝。

看一個簡單的示例就輕松的看懂了:

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ù)》中認(rèn)為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提供了完整的事務(wù)的執(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)
    }
}

介紹了這么多框架婚惫,最后需要說明的也可能最重要的是寫代碼時就應(yīng)該考慮代碼是可被測試的氛赐。要使得單元測試容易寫,或者說代碼容易被測辰妙,其實很重要的一個部分就是被測代碼本身是容易被測的鹰祸,也就是說在設(shè)計和編寫代碼的時候就應(yīng)該先想到相好如何單元測試甫窟,甚至有人提出可以先寫單元測試密浑,再寫具體被測代碼。因為一個接口(或者稱為單元)在被設(shè)計好后粗井,它實現(xiàn)就確定了尔破,實際效果也確定了。這種方式被稱作測試驅(qū)動開發(fā)(Test-Driven Development, TDD)浇衬。而對于已經(jīng)寫好的代碼懒构,很大程度上不好測試,有一種方式是測試性重構(gòu)耘擂,就是為了更好的測試而進(jìn)行重構(gòu)胆剧。這些一定程度上來說比了解這些框架更重要。
以上內(nèi)容就是本篇的全部內(nèi)容以上內(nèi)容希望對你有幫助醉冤,有被幫助到的朋友歡迎點贊秩霍,評論。
如果對軟件測試蚁阳、接口測試铃绒、自動化測試、面試經(jīng)驗交流螺捐。感興趣可以關(guān)注我颠悬,我們會有同行一起技術(shù)交流哦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末定血,一起剝皮案震驚了整個濱河市赔癌,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌澜沟,老刑警劉巖灾票,帶你破解...
    沈念sama閱讀 222,681評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異倔喂,居然都是意外死亡铝条,警方通過查閱死者的電腦和手機(jī)靖苇,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來班缰,“玉大人贤壁,你說我怎么就攤上這事〔和” “怎么了脾拆?”我有些...
    開封第一講書人閱讀 169,421評論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長莹妒。 經(jīng)常有香客問我名船,道長,這世上最難降的妖魔是什么旨怠? 我笑而不...
    開封第一講書人閱讀 60,114評論 1 300
  • 正文 為了忘掉前任渠驼,我火速辦了婚禮,結(jié)果婚禮上鉴腻,老公的妹妹穿的比我還像新娘迷扇。我一直安慰自己,他們只是感情好爽哎,可當(dāng)我...
    茶點故事閱讀 69,116評論 6 398
  • 文/花漫 我一把揭開白布蜓席。 她就那樣靜靜地躺著,像睡著了一般课锌。 火紅的嫁衣襯著肌膚如雪厨内。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,713評論 1 312
  • 那天渺贤,我揣著相機(jī)與錄音雏胃,去河邊找鬼。 笑死癣亚,一個胖子當(dāng)著我的面吹牛丑掺,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播述雾,決...
    沈念sama閱讀 41,170評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼街州,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玻孟?” 一聲冷哼從身側(cè)響起唆缴,我...
    開封第一講書人閱讀 40,116評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎黍翎,沒想到半個月后面徽,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,651評論 1 320
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,714評論 3 342
  • 正文 我和宋清朗相戀三年趟紊,在試婚紗的時候發(fā)現(xiàn)自己被綠了氮双。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,865評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡霎匈,死狀恐怖戴差,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情铛嘱,我是刑警寧澤暖释,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站墨吓,受9級特大地震影響球匕,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜帖烘,卻給世界環(huán)境...
    茶點故事閱讀 42,211評論 3 336
  • 文/蒙蒙 一亮曹、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蚓让,春花似錦乾忱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,699評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽衷佃。三九已至趟卸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間氏义,已是汗流浹背锄列。 一陣腳步聲響...
    開封第一講書人閱讀 33,814評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留惯悠,地道東北人邻邮。 一個月前我還...
    沈念sama閱讀 49,299評論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像克婶,于是被迫代替她去往敵國和親筒严。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,870評論 2 361