單元測試 Mock不Mock?
前言
前段時間,我們團隊就單元測試是否采用 Mock 進行了一番交流,各有各的說法。本文就單元測試 Mock不Mock 給出我的觀點,歡迎各位同仁提出不同的意見,共同探討、相互交流。
單元測試沒必要?
我見過好多不寫單元測試的項目,大多給出的原因都是:“沒必要”、“浪費時間”、“get不到單測的作用”,這樣的項目要么是小規模,要么就是頭鐵。
本人之前也有相同的觀點...
單元測試在軟件開發過程中還是非常重要的,除了可以提高代碼的質量,在引入CI/CD后的自動化測試環節可以起到快速部署、交付作用。難道每次上線都需要“點點點”測試?這一點,我想經歷過的人都深有體會。
Mock不Mock?
那什么是Mock?
什么是Mock?
簡單來說,Mock就是模擬目標代碼的行為,在實際測試過程中代替真實的調用目標。如下圖
圖片
這樣做的意義何在?
Mock的意義何在?
試想一下,單元測試中如果出現以下幾個問題應該怎么辦?
- 涉及到的DB操作、網絡調用等單元測試產生的數據屬不屬于垃圾數據?會不會影響業務?
- 發布/部署生產環境的過程中,錯誤地執行了單元測試引起生產問題怎么辦?
- CI過程中的測試環節花費時間太長怎么辦?會不會影響集成交付?
以上幾個問題我想大部分開發人員都經歷過,那如何避免這些問題?我想Mock就是最好的一種方式。
如果將涉及到的外部操作,例如DB操作、網絡調用等行為進行Mock,那就不會存在垃圾數據的問題,也不用擔心環境切換帶來的問題,外部耗時的操作也可以通過Mock避免CI過程過長。
個人認為Mock只是模擬調用外部的行為,并不影響代碼邏輯。所以,不存在“Mock是不是有效的單元測試”這種說法。
如何Mock?
應該Mock什么?
通常,我們編寫的方法(或函數)都是由很多方法按照層級組成的,就像這樣
圖片
當我們對頂層方法進行單元測試時,應該Mock哪些方法?
- 如果Mock方法1、2、3,那么方法4、5、6就不會被調用到,里面的邏輯不會被覆蓋到,也就不是有效的單元測試。
- 如果Mock方法4、5、6,里面的邏輯或返回值有修改,那么就要遞歸向上修改,不符合軟件工程。
但是,如果方法43、、5、6都涉及到DB或者網絡調用等外部不可控操作,我們就應該對其Mock。
所以,應該Mock一些穩定的、不可控的方法。
Mock 編寫示例
以Python中的Mock框架為例,下面是一個示例:
class TestXxService(unittest.TestCase):
def test_init(self):
XXService.update(xx)
class XXService:
def update(xx):
......
test_init函數中的....update會涉及到數據庫的操作,這里使用patch模擬這兩個函數的行為
# patch("目標函數路徑")
patch('....update')
在模擬的上下文中,XXService.update將會被模擬的函數替代執行
def test_init(self):
with patch('....update') as mocked_update:
# 在模擬的上下文中調用業務邏輯函數
XXService.update(xx)
mocked_update.assert_called_once_with(xx)
其中,assert_called_once_with 會驗證模擬函數是否被調用了一次,并且會驗證預期接收的參數是否匹配。
如果沒有參數,使用assert_called_once進行驗證是否被調用了一次。
如果模擬的函數實際被調用了多次,需要通過以下方式
# 斷言mocked_update被調用了2次
self.assertEqual(mocked_update.call_count, 2)
# 斷言mocked_update被調用了,并且參數正確
mocked_update.assert_any_call(xx)
如果函數有返回值,在定義模擬函數時,添加 return_value,return_value可以是任意類型。
patch('...update',
return_value='xxx') as mocked_update
在驗證返回值時通過下面的方式
xxxx = mocked_update.return_value
self.assertEqual(xxxx, 'xxx')
通過示例,我們Mock了XXService.update行為,實現了對XXService的隔離測試,并確保了測試的可靠性和高效性。
總結
單元測試中使用Mock有以下幾個好處:
- 隔離測試:Mock 使得測試可以專注于測試的代碼邏輯,而不必關心外部不穩定因素。
- 提高測試速度:Mock 可以避免耗時的外部調用,從而加快測試速度。
- 提高測試的可靠性和穩定性:通過Mock,可以避免外部變化對測試結果的影響。