【編者按】mock是一門技術,經過僞造部分實際代碼,從而讓開發者能夠驗證剩餘代碼的正確性。從而,經過mock,開發者能夠很是便捷地測試某個函數的內部代碼,下面就帶你穿梭mock。python
如下爲譯文函數
本博文主要聚焦mock的使用。mock是一門技術,經過僞造部分實際代碼,從而讓開發者能夠驗證剩餘代碼的正確性。下面將經過幾個簡單的示例演示mock在Python測試代碼中的使用,以及這項極其有用的技術是如何幫助開發者改善測試代碼的。單元測試
當進行單元測試時,目標每每是爲了測試很是小的代碼塊,例如一個獨立存在的函數或類方法。換句話說,代碼測試只針對指定函數的內部代碼。若是測試代碼須要依賴於其餘的代碼片斷,在某種不幸的情形下,即便被測試的函數沒有變化,這部份內嵌代碼的修改仍然可能破壞原有的測試。看看下面的例子,你將豁然開朗:測試
# function.py def add_and_multiply(x, y): addition = x + y multiple = multiply(x, y) return (addition, multiple) def multiply(x, y): return x * y # test.py import unittest class MyTestCase(unittest.TestCase): def test_add_and_multiply(self): x = 3 y = 5 addition, multiple = add_and_multiply(x, y) self.assertEqual(8, addition) self.assertEqual(15, multiple) if __name__ == "__main__": unittest.main()
$ python test.py . ---------------------------------------------------------------------- Ran 1 test in 0.001s OK
在上面的例子中,`add_and_multiply`計算兩個數的和與乘積並返回。「add_and_multiply」調用了另外一個函數「multiply」進行乘積計算。若是指望摒棄「傳統「的數學,並從新定義「multiply」函數,在原有的乘積結果上加3。新的「multiply」函數以下:spa
def multiply(x, y): return x * y + 3
如今就會遇到一個問題。即便測試代碼沒有變化,須要測試的函數也沒有變化,然而,「test_add_and_multiply」卻會執行失敗:code
$ python test.py F ====================================================================== FAIL: test_add_and_multiply (__main__.MyTestCase) ---------------------------------------------------------------------- Traceback (most recent call last): File "test.py", line 13, in test_add_and_multiply self.assertEqual(15, multiple) AssertionError: 15 != 18 ---------------------------------------------------------------------- Ran 1 test in 0.001s FAILED (failures=1)
這個問題之因此會發生,是由於原始測試代碼並不是真正的單元測試。儘管開發者指望測試的是外部函數,但每每隱性地將內部函數也包含進來,因指望結果是依賴於這個內部函數的行爲的。雖然在上面這個簡單的示例中呈現的差別顯得毫無心義,但某些場景下,咱們須要測試一個複雜的邏輯代碼塊,例如,一個Django視圖函數基於某些特定條件調用各類不一樣的內部功能,從函數調用結果中分離出視圖邏輯的測試就顯得尤其重要了。對象
解決這個問題有兩種方案。要麼忽略,像集成測試那樣去進行單元測試,要麼求助於mock。第一種方案的缺點是,集成測試僅僅告訴咱們函數調用時哪一行代碼出問題了,這樣很難找到問題根源所在。這並非說,集成測試沒有用處,由於在某些狀況下它確實很是有用。無論怎樣,單元測試和集成測試用於解決不一樣的問題,它們應該被同時使用。所以,若是想要成爲一個好的測試人員,mock是一個不錯的替代選擇。ip
mock是一個極其優秀的Python包,Python 3已將其歸入標準庫。對於咱們這些還在UnicodeError遍及的Python 2.x中掙扎的苦逼碼農,能夠經過pip進行安裝:開發
pip install mock==1.0.1
mock有多種不一樣的用法。咱們能夠用它提供猴子補丁功能,建立僞造的對象,甚至能夠做爲一個上下文管理器。全部這些都是基於一個共同目標的,用副本替換部分代碼來收集信息並返回僞造的響應。文檔
mock的[ 文檔]很是密集,尋找特定的用例信息可能會很是棘手。這裏,咱們就來看看一個常見的場景——替換一個內嵌函數並檢查它的輸入和輸出。
下面將使用mock從新編寫單元測試。接下來,會討論發生了什麼,以及爲何從測試的角度來看它是很是有用的:
# test.py import mock import unittest class MyTestCase(unittest.TestCase): @mock.patch('multiply') def test_add_and_multiply(mock_multiply): x = 3 y = 5 mock_multiply.return_value = 15 addition, multiple = add_and_multiply(x, y) mock_multiply.assert_called_once_with(3, 5) self.assertEqual(8, addition) self.assertEqual(15, multiple) if __name__ == "__main__": unittest.main()
至此,咱們能夠改變「multiply」函數來作任何事情——它可能返回加3後的乘積,返回None,或返回favourite line from Monty Python and the Holy Grail——你會發現,咱們上面的測試仍然能夠經過。這是由於咱們mock了「multiply」函數。在真正的單元測試場景下,咱們並不關心「multiply」函數內部發生了什麼,從測試「add_and_multiply」的角度來看,只關心「multiply」被正確的參數調用了。這裏咱們假定有另外一個單元測試會針對「multiply」的內部邏輯進行測試。
乍一看,上面的語法可能很差理解。下面逐行分析:
@mock.patch('multiply') def test_add_and_multiply(mock_multiply):
使用「mock.patch」裝飾器來用mock對象替換"multiply'。而後將它做爲一個參數"mock_multiply"插入到測試代碼中。在這個測試的上下文中,任何對「multiply」的調用都會被重定向到「mock_multiply」對象。
有人會質疑——「怎麼能用對象替換函數!」別擔憂!在Python的世界,函數也是對象。一般狀況下,當咱們調用「multiply()」,實際執行的是「multiply」函數的「__call__」方法。然而,恰當的使用mock,對「multiply()」的調用將執行mock對象,而不是「__call__」方法。
mock_multiply.return_value = 15
爲了使mock函數能夠返回任何東西,咱們須要定義其「return_value」屬性。實際上,當mock函數被調用時,它用於定義mock對象的返回值。
addition, multiple = add_and_multiply(x, y) mock_multiply.assert_called_once_with(3, 5)
在測試代碼中,咱們調用了外部函數「add_and_multiply」。它會調用內嵌的`multiply`函數,若是咱們正確的進行了mock,調用將會被定義的mock對象取代。爲了驗證這一點,咱們能夠用到mock對象的高級特性——當它們被調用時,傳給它們的任何參數將被儲存起來。顧名思義,mock對象的「assert_called_once_with」方法就是一個不錯的捷徑來驗證某個對象是否被一組特定的參數調用過。若是被調用了,測試經過。反之,「assert_called_once_with」會拋出`AssertionError`的異常。
這裏會遇到不少實際問題。首先,咱們經過mock將「multiply」函數從「add_and_multiply」中分離出來。這就意味着咱們的單元測試只針對「add_and_multiply」的內部邏輯。只有針對「add_and_multiply」的代碼修改將影響測試的成功與否。
其次,能夠控制內嵌函數的輸出,以確保外部函數處理了不一樣的狀況。例如,「add_and_multiply」可能有邏輯條件依賴於「multiply」的返回值:好比說,咱們只想在乘積大於10的條件下返回一個值。經過人爲設定「multiply」的返回值,咱們能夠模擬乘積小於10的狀況以及乘積大於10的狀況,從而能夠很容易測試咱們的邏輯正確性。
最後,咱們如今能夠驗證被mock的函數被調用的次數,並傳入了正確的參數。因爲咱們的mock對象取代了「multiply」函數的位置,咱們知道任何針對「multiply」函數的調用都會被重定向到該mock對象。當測試一個複雜的功能時,確保每一步都被正確調用將是一件很是使人欣慰的事情。