當前位置:
首頁 > 最新 > GoStub框架二次開發實踐

GoStub框架二次開發實踐

新媒體管家

每天讀一篇一線開發者原創好文

▍序言

要寫出好的測試代碼,必須精通相關的測試框架。對於Golang的程序員來說,至少需要掌握下面四個測試框架:

GoConvey

GoStub

GoMock

Monkey

儘管GoStub框架已經解決了很多場景的函數打樁問題,但對於一些複雜的情況,卻只能幹瞪眼:

1.被測函數中多次調用了資料庫讀操作函數介面 ReadDb,並且資料庫為key-value型。被測函數先是 ReadDb 了一個父目錄的值,然後在 for 循環中讀了若干個子目錄的值。在多個測試用例中都有將ReadDb打樁為在多次調用中呈現不同行為的需求,即父目錄的值不同於子目錄的值,並且子目錄的值也互不相等

2.被測函數中有一個循環,用於一個批量操作,當某一次操作失敗,則返回失敗,並進行錯誤處理。假設該操作為Apply,則在異常的測試用例中有將Apply打樁為在多次調用中呈現不同行為的需求,即Apply的前幾次調用返回成功但最後一次調用卻返回失敗

3.被測函數中多次調用了同一底層操作函數,比如 exec.Command,函數參數既有命令也有命令參數。被測函數先是創建了一個對象,然後查詢對象的狀態,在對象狀態達不到期望時還要刪除對象,其中查詢對象是一個重要的操作,一般會進行多次重試。在多個測試用例中都有將 exec.Command 打樁為多次調用中呈現不同行為的需求,即創建對象、查詢對象狀態和刪除對象對返回值的期望都不一樣

4....

針對GoStub框架不適用的複雜情況,本文將對該框架進行二次開發,優雅的變不適用為適用,提高GoStub框架的適應能力。

▍介面

根據開閉原則,我們通過新增介面來應對複雜情況,那麼應該增加兩個介面:

1.函數介面

2.方法介面

對於複雜情況,都是針對一個函數的多次調用而產生不同的行為,即存在多個返回值列表。顯然用戶打樁時應該指定一個數組切片[]Output,那麼數組切片的元素Output應該是什麼呢?

每一個函數的返回值列表的大小不是確定的,且返回值類型也不統一,所以Output本身也是一個數組切片,Output的元素是interface{}。

於是Output有了下面的定義:

對於函數介面的聲明如下所示:

對於方法介面的聲明如下所示:

但還存在下面兩種情況:

1.當被打樁函數在批量操作的場景下,即前面幾次都返回成功而最後一次卻返回失敗,outputs中存在多個相鄰的值是一樣的

2.當被打樁函數在重試調用的場景下,即被打樁函數在前面幾次都返回失敗而最後一次卻返回成功,outputs中存在多個相鄰的值是一樣的

重複是萬惡之源,我們保持零容忍,所以引入Times變數到Output中,於是Output的定義就演進為:

於是Output有了下面的定義:

▍介面使用場景一:多次讀資料庫假設我們在一個函數f中讀了3次資料庫,比如調用了3次函數ReadLeaf,即通過3個不同的url讀取了3個不同的value。ReadLeaf在db包中定義,示例如下:

假設對該函數打樁之前還未生成stubs對象,覆蓋3次讀資料庫的場景的打樁代碼如下:

說明:不指定Times時,Times的值為1

▍場景二:批量操作

假設我們在一個函數f中進行批量操作,比如在一個循環中調用了5次Apply函數,前4次操作都成功但第5次操作卻失敗了。Apply在resource包中定義,示例如下:

假設對該函數打樁之前已經生成了stubs對象,覆蓋前4次Apply都成功但第5次Apply卻失敗的場景的打樁代碼如下:

假設對該函數打樁之前已經生成了stubs對象,覆蓋前4次Apply都成功但第5次Apply卻失敗的場景的打樁代碼如下:

▍場景三:底層操作有重試

假設我們在一個函數f中調用了3次底層操作函數,比如調用了3次Command函數,即第一次調用創建對象,第二次調用查詢對象的狀態,在狀態達不到期望的情況下第三次掉用刪除對象,其中第二次調用時為了提高正確性,進行了10次嘗試。Command在exec包中定義,屬於庫函數,我們不能直接打樁,所以要在適配層adapter包中進行二次封裝:

假設對該函數打樁之前已經生成了stubs對象,覆蓋前9次嘗試失敗且第10次嘗試成功的場景的打樁代碼如下:

▍介面實現

函數介面實現

函數介面的實現很簡單,直接委託方法介面實現:提供函數介面的目的是,在Stubs對象生成之前就可以使用該介面。

▍方法介面實現

我們回顧一下方法介面的聲明:

方法介面的實現相對比較複雜,需要藉助反射和閉包這兩個強大的功能。

為了便於實現,我們分而治之,先進行to do list的拆分:

1.入參校驗。(1)funcVarToStub必須為指向函數的指針變數;(2)函數返回值列表的大小必須和Output.StubVals切片的長度相等

2.將outputs中的Times變數都消除,轉化成一個純的多組返回值列表,即切片[]Values,設切片變數為slice

3.構造一個閉包函數,自由變數為i,i的值為[0, len(slice) - 1],閉包函數的返回值列表為slice[i]

4.將待打樁函數替換為閉包函數

▍入參校驗

入參校驗的代碼參考了StubFunc方法的實現,如下所示:

構造slice構造slice的代碼很簡單,如下所示:

說明:當Times的值小於等於1時,就按1次記錄,否則按實際次數記錄。這是一個特殊處理,目的是用戶在構造Output時,一般不需要顯式的給Times賦值,除非有多次,這樣就提高了GoStub框架的易用性。

生成閉包

生成閉包的代碼實現中調用了新封裝的函數getResultValues,如下所示:

新封裝的函數getResultValues的實現參考了StubFunc方法的實現,如下所示:

說明:StubFuncSeq要求len(slice)必須大於等於樁函數的調用次數,否則會顯式panic,並有異常日誌"output seq is less than call seq!"。

▍將待打樁函數替換為閉包

這裡直接復用既有的變數打樁方法Stub即可實現,如下所示:

至此,StubFuncSeq方法實現完了,oh yeah!

▍反模式

多個測試用例的樁函數綁定在一起

通過上一篇文章《GoStub框架使用指南》的學習,讀者會寫出諸如下面的測試代碼:GoStub框架有了StubFuncSeq介面後,有些讀者就會將上面的測試代碼寫成下面的反模式:

有的讀者可能認為上面的測試代碼更好,但一般情況下,一個測試函數有多個測試用例,即第二級的Convey數(5個左右很常見)。如果將所有測試用例的樁函數都寫在一起,將非常複雜,而且很多時候會超過人腦的掌握極限,所以筆者將這種模式稱為反模式。

我們提倡每個用例管理自己的樁函數,即分離關注點。

函數返回值列表都相同仍使用StubFuncSeq介面打樁

顯然,StubFuncSeq介面的功能強於StubFunc介面,這就導致有些讀者習慣了使用StubFuncSeq介面,而忽略或很少使用StubFunc介面。

假設函數f中有一個循環,可以從數組切片中獲取到不同用戶的Id,然後根據Id清理該用戶的資源。比如總共有3個用戶,依次調用resource包中的Clear函數進行資源清理,該函數的示例如下:

假設對該函數打樁之前已經生成了stubs對象,覆蓋3次都清理成功的場景的打樁代碼如下:

這段代碼儘管沒毛病,但如果函數通過StubFunc介面打樁,則不管樁函數被調用多少次,都會返回唯一的值列表。

我們重構一下代碼:

很明顯,重構後的代碼簡單了很多。

可見,當函數返回值列表都相同時仍使用StubFuncSeq介面打樁是一種反模式。我們在給函數打樁時,優先使用StubFunc介面,當且僅當StubFunc介面不滿足測試需求時才考慮使用StubFuncSeq介面。

▍小結

針對GoStub框架不適用的複雜情況,本文對該框架進行了二次開發,包括新增介面StubFuncSeq的定義、使用及實現,優雅的變不適用為適用,提高了GoStub框架的適應能力。本文在最後還提出了StubFuncSeq介面使用的兩種反模式,使得讀者時刻保持警惕,從而正確的使用GoStub框架。

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 中興開發者社區 的精彩文章:

GoStub框架使用指南
乾貨 麵包屑的故事

TAG:中興開發者社區 |