GoStub框架使用指南

序言

要寫出好的測(cè)試代碼作煌,必須精通相關(guān)的測(cè)試框架。對(duì)于Golang的程序員來說僧凤,至少需要掌握下面四個(gè)測(cè)試框架:

  • GoConvey
  • GoStub
  • GoMock
  • Monkey

通過上一篇文章《GoConvey框架使用指南》的學(xué)習(xí),大家熟悉了GoConvey框架的基本使用方法,雖然已經(jīng)可以寫出簡單優(yōu)雅的測(cè)試代碼芥映,但是如果在被測(cè)函數(shù)中調(diào)用了底層操作函數(shù),比如調(diào)用了os包的Stat函數(shù)远豺,則需要在測(cè)試函數(shù)中先對(duì)該底層操作函數(shù)打樁奈偏。那么,該如何對(duì)函數(shù)高效的打樁呢躯护?

本文給大家介紹一款輕量級(jí)的GoStub框架惊来,接口友好,可以對(duì)全局變量棺滞、函數(shù)或過程打樁裁蚁,我們一起來體驗(yàn)一下。

安裝

在命令行運(yùn)行命令:

go get github.com/prashantv/gostub

運(yùn)行完后你會(huì)發(fā)現(xiàn)继准,在$GOPATH/src/github.com目錄下枉证,新增了prashantv/gostub子目錄,這就是本文的主角移必。

使用場(chǎng)景

GoStub框架的使用場(chǎng)景很多室谚,依次為:

  1. 基本場(chǎng)景:為一個(gè)全局變量打樁
  2. 基本場(chǎng)景:為一個(gè)函數(shù)打樁
  3. 基本場(chǎng)景:為一個(gè)過程打樁
  4. 復(fù)合場(chǎng)景:由任意相同或不同的基本場(chǎng)景組合而成

為一個(gè)全局變量打樁

假設(shè)num為被測(cè)函數(shù)中使用的一個(gè)全局整型變量,當(dāng)前測(cè)試用例中假定num的值大于100,比如為150舞萄,則打樁的代碼如下:

stubs := Stub(&num, 150)
defer stubs.Reset()

stubs是GoStub框架的函數(shù)接口Stub返回的對(duì)象眨补,該對(duì)象有Reset操作管削,即將全局變量的值恢復(fù)為原值倒脓。

為一個(gè)函數(shù)打樁

假設(shè)我們產(chǎn)品的既有代碼中有下面的函數(shù)定義:

func Exec(cmd string, args ...string) (string, error) {
    ...
}

則Exec函數(shù)是不能通過GoStub框架打樁的。

若要想對(duì)Exec函數(shù)通過GoStub框架打樁含思,則僅需對(duì)該函數(shù)聲明做很小的重構(gòu)崎弃,即將Exec函數(shù)定義為匿名函數(shù),同時(shí)將它賦值給Exec變量含潘,重構(gòu)后的代碼如下:

var Exec = func(cmd string, args ...string) (string, error) {
    ...
}

說明:對(duì)于新增函數(shù)饲做,請(qǐng)按上面的方式定義

當(dāng)Exec函數(shù)重構(gòu)成Exec變量后,絲毫不影響既有代碼中對(duì)Exec函數(shù)的調(diào)用遏弱。由于Exec變量是函數(shù)變量盆均,所以我們一般將這類變量也叫做函數(shù)。

現(xiàn)在我們可以對(duì)Exec函數(shù)打樁了漱逸,代碼如下所示:

stubs := Stub(&Exec, func(cmd string, args ...string) (string, error) {
            return "xxx-vethName100-yyy", nil
})
defer stubs.Reset()

其實(shí)GoStub框架專門提供了StubFunc函數(shù)用于函數(shù)打樁泪姨,我們重構(gòu)打樁代碼:

stubs := StubFunc(&Exec,"xxx-vethName100-yyy", nil)
defer stubs.Reset()

產(chǎn)品代碼中很多函數(shù)都會(huì)調(diào)用Golang的庫函數(shù)或第三方的庫函數(shù),我們又不能重構(gòu)這些函數(shù)饰抒,那么該如何對(duì)這些庫函數(shù)打樁肮砾?

答案很簡單,即在適配層中定義庫函數(shù)的變量袋坑,然后在產(chǎn)品代碼中使用該變量仗处。

定義庫函數(shù)的變量:

package adapter

var Stat = os.Stat
var Marshal = json.Marshal
var UnMarshal = json.Unmarshal
...

使用UnMarshal的代碼:

bytes, err := adapter.Marshal(&student)
if err != nil {
    ...
    return err
}
...
...

我們現(xiàn)在可以對(duì)庫函數(shù)進(jìn)行打樁了。假設(shè)當(dāng)前使用的庫函數(shù)為Marshal枣宫,因?yàn)镸arshal函數(shù)有成功或失敗兩種情況婆誓,所以它有兩個(gè)樁函數(shù),但對(duì)于每一個(gè)測(cè)試用例來說Unmarshal只有一個(gè)樁函數(shù)也颤。

序列化成功時(shí)的打樁代碼為:

var liLei = `{"name":"LiLei", "age":"21"}`
stubs := StubFunc(&adapter.Marshal, []byte(liLei), nil)
defer stubs.Reset()

序列化失敗時(shí)的打樁代碼為:

stubs := StubFunc(&adapter.Marshal, nil, ErrAny)
defer stubs.Reset()

為一個(gè)過程打樁

當(dāng)一個(gè)函數(shù)沒有返回值時(shí)洋幻,該函數(shù)我們一般稱為過程。很多時(shí)候歇拆,我們將資源清理類函數(shù)定義為過程鞋屈。

我們對(duì)過程DestroyResource的打樁代碼為:

stubs := StubFunc(&DestroyResource)
defer stubs.Reset()

任意相同或不同的原子場(chǎng)景的組合

不論是調(diào)用Stub函數(shù)還是StubFunc函數(shù),都會(huì)生成一個(gè)stubs對(duì)象故觅,該對(duì)象仍然有Stub方法和StubFunc方法厂庇,所以在一個(gè)測(cè)試用例中可以同時(shí)對(duì)多個(gè)全局變量、函數(shù)或過程打樁输吏。這些全局變量权旷、函數(shù)或過程會(huì)將初始值存在一個(gè)map中,并在延遲語句中通過Reset方法統(tǒng)一做回滾處理。

假設(shè)Sf為Stub或StubFunc函數(shù)的調(diào)用拄氯,Sm為Stub或StubFunc方法的調(diào)用躲查,則在一個(gè)測(cè)試用例中使用GoStub框架的打樁代碼為:

stubs := Sf
defer stubs.Reset()
stubs.Sm1
...
stubs.SmN

不推薦將打樁代碼寫成下面的形式:

stubs := Sf
defer stubs.Sm1.(...).SmN.Reset()

TestFuncDemo

筆者在上一篇文章《GoConvey框架使用指南》中推薦讀者使用Convey語句的嵌套,即一個(gè)函數(shù)有一個(gè)測(cè)試函數(shù)译柏,測(cè)試函數(shù)中嵌套兩級(jí)Convey語句镣煮,第一級(jí)Convey語句對(duì)應(yīng)測(cè)試函數(shù),第二級(jí)Convey語句對(duì)應(yīng)測(cè)試用例鄙麦。在第二級(jí)的每個(gè)Convey函數(shù)中都會(huì)產(chǎn)生一個(gè)stubs對(duì)象典唇,彼此獨(dú)立,互不影響胯府。

我們看一個(gè)針對(duì)GoStub框架使用的較為完整的測(cè)試函數(shù)Demo:

func TestFuncDemo(t *testing.T) {
    Convey("TestFuncDemo", t, func() {
        Convey("for succ", func() {
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec,"xxx-vethName100-yyy", nil)
            var liLei = `{"name":"LiLei", "age":"21"}`
            stubs.StubFunc(&adapter.Marshal, []byte(liLei), nil)
            stubs.StubFunc(&DestroyResource)
            //several So assert
        })

        Convey("for fail when num is too small", func() {
            stubs := Stub(&num, 50)
            defer stubs.Reset()
            //several So assert
        })

        Convey("for fail when Exec error", func() {
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec, "", ErrAny)
            //several So assert
        })

        Convey("for fail when Marshal error", func() {
            stubs := Stub(&num, 150)
            defer stubs.Reset()
            stubs.StubFunc(&Exec,"xxx-vethName100-yyy", nil)
            stubs.StubFunc(&adapter.Marshal, nil, ErrAny)
            //several So assert
        })

    })
}

不適用的復(fù)雜情況

盡管GoStub框架已經(jīng)可以優(yōu)雅的解決很多場(chǎng)景的函數(shù)打樁問題介衔,但對(duì)于一些復(fù)雜的情況,卻只能干瞪眼:

  1. 被測(cè)函數(shù)中多次調(diào)用了數(shù)據(jù)庫讀操作函數(shù)接口 ReadDb骂因,并且數(shù)據(jù)庫為key-value型炎咖。被測(cè)函數(shù)先是 ReadDb 了一個(gè)父目錄的值,然后在 for 循環(huán)中讀了若干個(gè)子目錄的值寒波。在多個(gè)測(cè)試用例中都有將ReadDb打樁為在多次調(diào)用中呈現(xiàn)不同行為的需求乘盼,即父目錄的值不同于子目錄的值,并且子目錄的值也互不相等

  2. 被測(cè)函數(shù)中有一個(gè)循環(huán)影所,用于一個(gè)批量操作蹦肴,當(dāng)某一次操作失敗,則返回失敗猴娩,并進(jìn)行錯(cuò)誤處理阴幌。假設(shè)該操作為Apply,則在異常的測(cè)試用例中有將Apply打樁為在多次調(diào)用中呈現(xiàn)不同行為的需求卷中,即Apply的前幾次調(diào)用返回成功但最后一次調(diào)用卻返回失敗

  3. 被測(cè)函數(shù)中多次調(diào)用了同一底層操作函數(shù)矛双,比如 exec.Command,函數(shù)參數(shù)既有命令也有命令參數(shù)蟆豫。被測(cè)函數(shù)先是創(chuàng)建了一個(gè)對(duì)象议忽,然后查詢對(duì)象的狀態(tài),在對(duì)象狀態(tài)達(dá)不到期望時(shí)還要?jiǎng)h除對(duì)象十减,其中查詢對(duì)象是一個(gè)重要的操作栈幸,一般會(huì)進(jìn)行多次重試。在多個(gè)測(cè)試用例中都有將 exec.Command 打樁為多次調(diào)用中呈現(xiàn)不同行為的需求帮辟,即創(chuàng)建對(duì)象速址、查詢對(duì)象狀態(tài)和刪除對(duì)象對(duì)返回值的期望都不一樣

  4. ...

小結(jié)

GoStub是一款輕量級(jí)的測(cè)試框架,接口友好由驹,可以對(duì)全局變量芍锚、函數(shù)或過程打樁。本文詳細(xì)闡述了GoStub框架的使用場(chǎng)景,并給出了一個(gè)較為完整的測(cè)試函數(shù)Demo并炮,希望讀者能夠掌握GoStub框架的基本使用方法默刚,提高單元測(cè)試水平,交付高質(zhì)量的軟件逃魄。

針對(duì)GoStub框架不適用的復(fù)雜情況荤西,筆者將該框架進(jìn)行了二次開發(fā),優(yōu)雅的解決了問題嗅钻,我們?cè)谙乱黄恼轮薪o出答案皂冰。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市养篓,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌赂蕴,老刑警劉巖柳弄,帶你破解...
    沈念sama閱讀 221,548評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異概说,居然都是意外死亡碧注,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門糖赔,熙熙樓的掌柜王于貴愁眉苦臉地迎上來萍丐,“玉大人,你說我怎么就攤上這事放典∈疟洌” “怎么了?”我有些...
    開封第一講書人閱讀 167,990評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵奋构,是天一觀的道長壳影。 經(jīng)常有香客問我,道長弥臼,這世上最難降的妖魔是什么宴咧? 我笑而不...
    開封第一講書人閱讀 59,618評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮径缅,結(jié)果婚禮上掺栅,老公的妹妹穿的比我還像新娘。我一直安慰自己纳猪,他們只是感情好氧卧,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著兆旬,像睡著了一般假抄。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,246評(píng)論 1 308
  • 那天宿饱,我揣著相機(jī)與錄音熏瞄,去河邊找鬼。 笑死谬以,一個(gè)胖子當(dāng)著我的面吹牛强饮,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播为黎,決...
    沈念sama閱讀 40,819評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼邮丰,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了铭乾?” 一聲冷哼從身側(cè)響起剪廉,我...
    開封第一講書人閱讀 39,725評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎炕檩,沒想到半個(gè)月后斗蒋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,268評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡笛质,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評(píng)論 3 340
  • 正文 我和宋清朗相戀三年泉沾,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片妇押。...
    茶點(diǎn)故事閱讀 40,488評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡跷究,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出敲霍,到底是詐尸還是另有隱情俊马,我是刑警寧澤,帶...
    沈念sama閱讀 36,181評(píng)論 5 350
  • 正文 年R本政府宣布色冀,位于F島的核電站潭袱,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏锋恬。R本人自食惡果不足惜屯换,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評(píng)論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望与学。 院中可真熱鬧彤悔,春花似錦、人聲如沸索守。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽卵佛。三九已至杨赤,卻和暖如春敞斋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背疾牲。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評(píng)論 1 272
  • 我被黑心中介騙來泰國打工植捎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人阳柔。 一個(gè)月前我還...
    沈念sama閱讀 48,897評(píng)論 3 376
  • 正文 我出身青樓焰枢,卻偏偏與公主長得像,于是被迫代替她去往敵國和親舌剂。 傳聞我的和親對(duì)象是個(gè)殘疾皇子济锄,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 序言 要寫出好的測(cè)試代碼,必須精通相關(guān)的測(cè)試框架霍转。對(duì)于Golang的程序員來說荐绝,至少需要掌握下面四個(gè)測(cè)試框架: G...
    _張曉龍_閱讀 23,768評(píng)論 7 43
  • 序言 要寫出好的測(cè)試代碼,必須精通相關(guān)的測(cè)試框架谴忧。對(duì)于Golang的程序員來說很泊,至少需要掌握下面四個(gè)測(cè)試框架: G...
    _張曉龍_閱讀 4,174評(píng)論 1 13
  • 序言 在軟件開發(fā)中,產(chǎn)品代碼的正確性通過測(cè)試代碼來保證沾谓,而測(cè)試代碼的正確性誰來保證?答案是毫無爭議的戳鹅,肯定是程序員...
    _張曉龍_閱讀 53,298評(píng)論 7 67
  • 第5章 引用類型(返回首頁) 本章內(nèi)容 使用對(duì)象 創(chuàng)建并操作數(shù)組 理解基本的JavaScript類型 使用基本類型...
    大學(xué)一百閱讀 3,238評(píng)論 0 4
  • 序言 要寫出好的測(cè)試代碼均驶,必須精通相關(guān)的測(cè)試框架。對(duì)于Golang的程序員來說枫虏,至少需要掌握下面四個(gè)測(cè)試框架: G...
    Aedan閱讀 2,301評(píng)論 0 0