楔子
現在, 要測試這些接口:

""" 用例集 case_set.py pip install requests """ import requests def v2ex_info(): """ 獲取v2ex的網站信息 https://www.v2ex.com/api/site/info.json """ response = requests.get(url='https://www.v2ex.com/api/site/info.json') return response.json().get('title') # V2EX def v2ex_stats(): """ 獲取v2ex的網站信息 https://www.v2ex.com/api/site/stats.json """ response = requests.get(url='https://www.v2ex.com/api/site/stats.json') return response.json().get('member_max') # int類型 def cnodejs(): """ 獲取 cnodejs,推薦博客總數 """ response = requests.get('https://cnodejs.org/api/v1/topics') return response.json().get('success') # True if __name__ == '__main__': print(v2ex_info() == 'V2EX') print(type(v2ex_stats()) is int) print(cnodejs() is True)
關於requests模塊, see also:https://www.cnblogs.com/sundawei7/p/11949153.html
規則是:
- v2ex_info接口返回值中的title是
V2EX
才算通過。 - v2ex_stats接口返回值中的member_max是int類型才算通過。
- cnodejs接口只值中的success是True算通過。
根據規則很快的寫出了測試用例:

""" 用例類 myMain.py """ import unittest from case_set import v2ex_stats, v2ex_info, cnodejs class InterfaceCase(unittest.TestCase): def test_v2ex_stats(self): """ 測試 v2ex_stats 接口,返回: int類型""" self.assertIs(type(v2ex_stats()), int) def test_v2ex_info(self): """ 測試 v2ex_info 接口, 返回: V2EX """ self.assertEqual(v2ex_info(), 'V2EX') def test_cnodejs(self): """ 測試 cnodejs 接口,返回: True """ self.assertIs(cnodejs(), True) if __name__ == '__main__': unittest.main()
結果也OK:

M:\tests>python36 myMain.py -v test_cnblogs_info (__main__.InterfaceCase) 測試博客園接口,返回: 200 ... ok test_v2ex_info (__main__.InterfaceCase) 測試 v2ex_info 接口, 返回: V2EX ... ok test_v2ex_stats (__main__.InterfaceCase) 測試 v2ex_stats 接口,返回: int類型 ... ok ---------------------------------------------------------------------- Ran 3 tests in 3.075s OK
為什么需要mock
在反復的執行測試用例時,發現test_cnodejs
用例執行有些問題。有時候執行失敗有時候成功,並且就算成功也響應時間較長,一番分析后,發現不是自己的問題,是接口暫時開發的不是很完善,導致現在測試不穩定。 但是你根據接口文檔知道,這個接口這么測試,只要返回True就算通過。 那么能不能我們自己模擬出來這么一個接口,然后模擬一些方法和數據,在測試環境下使用。
什么是mock 在協同開發、測試中,總會出現各種問題,比如:
- 開發人員某些接口還沒有開發完畢。
- 與第三方聯調時,第三方拖了后腿,沒准備好環境、數據都有可能。比如說我們測試的某個接口本身沒有問題,但它依賴的某個接口有些問題,這就影響我們的正常測試任務進度。
- 測試環境惡劣。
- 開發只提供接口,數據自己搞!
這些問題總能影響我們的測試進度,那么我們怎么正常的展開呢? 這就需要mock來解決了。
什么是mock mock是在測試過程中,對於一些不容易構造/獲取的對象,創建一個mock對象來模擬對象的行為。 mock測試一般也稱為mock數據。 簡單來說,mock就是向測試對象提供一套和測試資源完全相同的接口和方法,不關系具體的實現過程,只關心具體結果。 mock測試的優點
- 團隊並行工作:有了mock,前后端人員只需要定義好接口文檔就可以開始並行的工作,互不影響,只需要在最后聯調的時候多多交流即可。后端與后端之間如果有接口耦合,也同樣能被Mock解決;測試過程中如果遇到依賴接口沒有准備好,同樣可以借助Mock;不會出現一個團隊等待另一個團隊的情況。這樣的話,開發自測階段就可以及早開展,從而發現缺陷的時機也提前了,有利於整個產品質量以及進度的保證。
- 開啟TDD模式,即測試驅動開發:單元測試是TDD實現的基石,而TDD經常會碰到協同模塊尚未開發完成的情況,但是有了mock,這些一切都不是問題。當接口定義好后,測試人員就可以創建一個Mock,把接口添加到自動化測試環境,提前創建測試。
- 模擬出無法訪問的資源:比如說,你需要調用一個“牆”外的資源來方便自己調試,就可以自己Mock一個。
- 系統隔離:假如我們需要調用一個post請求,為了獲得某個響應,來看當前系統是否能正確處理返回的“響應”,但是這個post請求會造成數據庫中數據的污染,那么就可以充分利用Mock,構造一個虛擬的post請求,我們給他指定返回就好了。
- 產品展示:假如我們需要創建一個演示程序,並且做了簡單的UI,那么在完全沒有開發后端服務的情況下,也可以進行演示。說到演示了,假如你已經做好了一個系統,並且需要給客戶進行演示,但是里面有些真實數據並不想讓用戶看到,那么同樣,你可以用Mock接口把這些敏感信息接口全部替換。
- 測試覆蓋:假如有一個接口,有100個不同類型的返回,我們需要測試它在不同返回下,系統是否能夠正常響應,但是有些返回在正常情況下基本不會發生,難道你要千方百計地給系統做各種手腳讓他返回以便測試嗎?比如,我們需要測試在當接口發生500錯誤的時候,app是否崩潰,別告訴我你一定要給服務端代碼做些手腳讓他返回500 。。。而使用mock,這一切就都好辦了,想要什么返回就模擬什么返回,媽媽再也不用擔心你的測試覆蓋度了。
關於TDD,see also:https://baike.baidu.com/item/TDD/9064369?fr=aladdin關於測試覆蓋,see also:https://www.cnblogs.com/sundawei7/p/11944489.html
如何mock數據
下載安裝
這里需要用到mock模塊了,在Python3.x中,mock被集成到了unittest中,無需下載,直接導入即可,但在Python2.x中,就需要:
pip install mock
mock類的構成
這里以Python3.x為例。
快速上手
構造器:_init_

from unittest import mock mock_obj = mock.Mock() print(mock_obj) # <Mock id='10069264'> print(dir(mock_obj)) ''' [ 'assert_any_call', 'assert_called', 'assert_called_once', 'assert_called_once_with', 'assert_called_with', 'assert_has_calls', 'assert_not_called', 'attach_mock', 'call_args', 'call_args_list', 'call_count', 'called', 'configure_mock', 'method_calls', 'mock_add_spec', 'mock_calls', 'reset_mock', 'return_value', 'side_effect' ] '''
雖然__init__
是實例化方法,但在這里通常被稱為構造器。 由打印結果可以看到,通過mock.Mock()
實例化出一個mock對象mock_obj
。這個對象是繼承了Mock類的屬性和方法。這樣的一個mock對象對我們來說用處不大。 我們來試着添加一些自定義屬性和方法,使之更靈活。 在Mock實例化時,我們可以傳入這些參數:
- name:mock對象的名字。它只是起到標識作用,當你print一個有name的mock對象時,可以看到它的name。
- spec:mock對象的屬性值。
- side_effect:該參數指向一個可調用對象(一般是函數),當mock對象被調用時,如果該參數的返回值是默認的DEFAULT,則mock對象返回return_value指定的值,否則返回side_effect指定的對象的返回值。
- return_value:該參數指定一個值或者對象,當mock對象被調用時,如果side_effect函數的返回值是DEFAULT,那么mock對象返回return_value指定的值或者對象。
注意,如果side_effect和return_value同時存在的時候,side_effect將會覆蓋return_value。
name

from unittest import mock mock_obj1 = mock.Mock() mock_obj2 = mock.Mock(name='mock_obj2') print(mock_obj1) # <Mock id='50111760'> print(mock_obj2) # <Mock name='mock_obj' id='53781776'>
name
參數沒啥好說的,就是跟mock對象起了個名字。
為return_value指定某個值 現在讓我們使用mock來模擬出文章開頭的那幾個接口測試中的cnodejs
接口。

import unittest from unittest import mock from case_set import cnodejs # 導入真實的cnodejs接口函數 class CnodejsTestCase(unittest.TestCase): def test_mock_cnodejs(self): """ 使用 mock 模擬的 cnodejs 接口 返回: True""" # 構造mock對象 cnodejs = mock.Mock(return_value=True) # 使用mock對象進行斷言 self.assertIs(cnodejs(), True) def test_cnodejs(self): """ 測試 cnodejs 接口,返回: True """ self.assertIs(cnodejs(), True) if __name__ == '__main__': unittest.main()
用例test_mock_cnodejs
方法中: 在Mock類實例化時傳入return_value
參數,然后構造出的mock對象賦值給cnodejs
變量。然后cnodejs()
相當於調用mock對象,得到返回值True
,完事拿着這個返回值使用unittest進行斷言。 用例test_cnodejs
方法中,正常寫測試用例斷言,以判斷兩個用例方法有什么不同之處:

test_cnodejs (__main__.CnodejsTestCase) 測試 cnodejs 接口,返回: True ... ok test_mock_cnodejs (__main__.CnodejsTestCase) 使用 mock 模擬的 cnodejs 接口 返回: True ... ok ---------------------------------------------------------------------- Ran 2 tests in 1.097s OK
可以看到,兩個用例方法都通過了,並沒有什么區別。 在測試環境下,使用mock模擬的方法進行測試,這樣能盡早的介入測試,帶來的優勢不一而足。 為return_value指定類的對象 return_value
除了上述用法,還可以指定類的對象:

from unittest import mock class Foo(object): """ 自定義類 """ def f1(self): return 'this is Foo.f1' def f2(self, name): return name # 正常的類的實例化與調用 foo_obj = Foo() print(foo_obj.f1()) # this is Foo.f1 print(foo_obj.f2('this is Foo.f2')) # this is Foo.f2 # 構造mock對象並傳入 Foo實例化對象 foo_Class = mock.Mock(return_value=Foo()) # 想要得到mock對象的返回值,必須調用,也就是加括號 foo_obj = foo_Class() # mock對象調用得到return_value值也就是Foo的實例化對象 # 接下里就是正常的調用了 print(foo_obj.f1()) # this is Foo.f1 # 同樣可以正常傳參 print(foo_obj.f2('this is Foo.f2')) # this is Foo.f2
使用mock對象模擬類的實例化對象同樣方便。
side_effect 先來看第一個示例,可以為mock對象的side_effect
參數指定可迭代對象。

from unittest import mock mock_obj1 = mock.Mock(return_value=100) print(mock_obj1()) # 100 mock_obj2 = mock.Mock(return_value=100, side_effect=[200, 300]) print(mock_obj2()) # 200
由上例可以看到,如果在構造mock對象的時候,只有return_value
被指定,調用mock對象返回return_value
指定的值。 當side_effect
和return_value
同時被指定時,side_effect
就覆蓋了return_value
。 那么既然side_effect
接受的是一個可迭代對象,就可以多次調用它:

from unittest import mock mock_obj1 = mock.Mock(return_value=100) print(mock_obj1()) # 100 mock_obj2 = mock.Mock(return_value=100, side_effect=[200, 300]) print(mock_obj2()) # 200 print(mock_obj2()) # 300 print(mock_obj2()) # StopIteration
可以看到side_effect
對象本質上是一個生成器。
為spec指定屬性組成的列表 現在使用mock來模擬出來兩V2EX的兩個接口方法。

import unittest from unittest import mock from case_set import v2ex_info, v2ex_stats # 為mock對象的spec參數傳入屬性(方法)組成的列表 spec_list = ['v2ex_info', 'v2ex_stats'] mock_obj = mock.Mock(spec=spec_list) print(spec_list) # ['v2ex_info', 'v2ex_stats'] # 根據真實的接口規則設置兩個方法的返回值 mock_obj.v2ex_info.return_value = 'V2EX' mock_obj.v2ex_stats.return_value = 466668 # 該接口只需要返回值是int即可 class TestCaseDemo(unittest.TestCase): def test_v2ex_stats(self): """ 測試 v2ex_stats 接口,返回: int類型""" self.assertIs(type(v2ex_stats()), int) def test_mock_v2ex_stats(self): """ mock v2ex_stats 接口,返回: int類型 """ v2ex_stats = mock_obj.v2ex_stats self.assertIs(type(v2ex_stats()), int) def test_mock_v2ex_info(self): """ mock v2ex_info 接口, 返回: V2EX """ v2ex_info = mock_obj.v2ex_info self.assertEqual(v2ex_info(), 'V2EX') def test_v2ex_info(self): """ 測試 v2ex_info 接口, 返回: V2EX """ self.assertEqual(v2ex_info(), 'V2EX') if __name__ == '__main__': unittest.main()
結果:

M:\tests>python36 myMain.py -v test_mock_v2ex_info (__main__.TestCaseDemo) mock v2ex_info 接口, 返回: V2EX ... ok test_mock_v2ex_stats (__main__.TestCaseDemo) mock v2ex_stats 接口,返回: int類型 ... ok test_v2ex_info (__main__.TestCaseDemo) 測試 v2ex_info 接口, 返回: V2EX ... ok test_v2ex_stats (__main__.TestCaseDemo) 測試 v2ex_stats 接口,返回: int類型 ... ok ---------------------------------------------------------------------- Ran 4 tests in 3.154s OK
由結果發現,用mock模擬的兩個接口都通過了。 為spec指定類屬性

from unittest.mock import Mock class Foo(object): age = 20 def f1(self): return 'this if f1' def f2(self, name): return name mock_obj = Mock(spec=Foo) print(mock_obj.f1) # <Mock name='mock.f1' id='1847131683640'> print(mock_obj.f2) # <Mock name='mock.f2' id='1847131615128'> print(mock_obj.age) # <Mock name='mock.age' id='1847131718880'> print(mock_obj.name) # AttributeError: Mock object has no attribute 'name'
為mock對象指定了屬性為Foo類,那么,類中的方法和屬性都是mock對象的屬性,這也是前三個打印沒有問題的原因,而第4個打印報錯了,顯然,Foo類中沒有一個叫name的屬性或者方法。
mock斷言語句
由mock思維導圖知道,mock關於斷言有這些常用的:
- assert_called_with(arg):檢查函數調用參數是否正確。
- assert_called_once_with(arg):檢查函數調用參數是否正確,但是只調用一次。
- assert_any_call():用於檢查測試的mock對象在測試例程中是否調用了方法。
- assert_has_calls():期望調用方法列表。
assert_called_with assert_called_with
檢查mock方法是否獲取了正確的參數,當至少有一個參數有錯誤的值或者類型時、當參數的個數出錯時、當參數的順序不正確時,斷言失敗。

from unittest.mock import Mock class Foo(object): value = 20 def f1(self, arg): return arg def f2(self, *args): return args mock_obj = Mock(spec=Foo) # f1正確的傳參姿勢 mock_obj.f1(222) # mock_obj.f1.assert_called_with() # 報錯,沒有傳參 # mock_obj.f1.assert_called_with(11) # 報錯,瞎98傳參 # mock_obj.f1.assert_called_with('6669') # 報錯,6翻了吧,傳值的類型不對 # mock_obj.f1.assert_called_with(222) # 噢啦,mock_obj.f1()傳的就是 222 # f2正確傳參姿勢 mock_obj.f2(1, 2, 3) # mock_obj.f2.assert_called_with() # 報錯,沒有傳參 # mock_obj.f2.assert_called_with(1) # 報錯,少傳了參數 # mock_obj.f2.assert_called_with(1, 3, 2) # 報錯,傳參順序不對 mock_obj.f2.assert_called_with(1, 2, 3) # 噢啦,傳參姿勢很對
assert_called_once_with assert_called_once_with
斷言,當指定方法被多次調用的時候,斷言失敗。

from unittest.mock import Mock class Foo(object): value = 20 def f1(self, arg): return arg def f2(self, *args): return args # 實例化mock對象 mock_obj = Mock(spec=Foo) # 為f1方法賦返回值 mock_obj.f1.return_value = 222 print(mock_obj.f1()) mock_obj.f1.assert_called_once_with() # 第一次調用,沒問題 print(mock_obj.f1()) mock_obj.f1.assert_called_once_with() # 第二次調用,報錯 AssertionError: Expected 'f1' to be called once. Called 2 times.
這個斷言相對簡單。
assert_any_call assert_any_call
斷言用於檢查測試執行中的mock對象在測試中是否調用了方法。

from unittest.mock import Mock class Foo(object): value = 20 def f1(self, arg): return arg def f2(self, *args): return args mock_obj = Mock(spec=Foo) # mock對象調用了 f1() f1(100) f1(200) f1(200) mock_obj.f1() mock_obj.f1(100) mock_obj.f1(200) mock_obj.f1(200) # 判斷:mock對象調用了f1() f1(100) f1(200) f1(300) f2() mock_obj.f1.assert_any_call() # 沒錯 mock_obj.f1.assert_any_call(100) # 沒錯 mock_obj.f1.assert_any_call(200) # 沒錯 # mock_obj.f1.assert_any_call(300) # AssertionError: f1(300) call not found mock_obj.f2.assert_any_call() # AssertionError: f2() call not found
上例,assert_any_call
會判斷整個測試中方法是否被調用了。而不管該方法是否被重復調用。 例如,在程序執行時執行了mock_obj.f1.assert_any_call()
,那么就用mock_obj.f1.assert_any_call()
判斷剛才的方法是否執行過。執行過啥都不做,要是沒執行過就報錯。
assert_has_calls assert_has_calls
檢查是否按照正確的順序和正確的參數進行調用的。所以,需要給出一個方法的調用順序,assert的時候按照這個順序進行檢查。

from unittest.mock import Mock from unittest.mock import call # 引入新的模塊 class Foo(object): value = 20 def f1(self, arg): return arg mock_obj = Mock(spec=Foo) # 正確的執行順序是 f1() f1(100) f1(200) mock_obj.f1() mock_obj.f1(100) mock_obj.f1(200) # 報錯, 現在的執行順序是 f1() f1(100) f1(300) # calls_list = [call.f1(), call.f1(100), call.f1(300)] # 報錯,沒有 call.f1(300) # mock_obj.assert_has_calls(calls_list) # 報錯,現在的執行順序是 f1(200) f1() f1(300) # calls_list = [call.f1(200), call.f1(), call.f1(200)] # 報錯,執行順序不對 # mock_obj.assert_has_calls(calls_list) # 對嘍 calls_list = [call.f1(), call.f1(100), call.f1(200)] mock_obj.assert_has_calls(calls_list)
首先,以列表的形式列出方法調用順序,每個方法前使用call.f1()
的形式,因為如果不加call
來修飾的話, 解釋器將不知道f1
是一個方法,當然call
在使用之前需要引入。
mock管理方法
mock中,關於管理有這些常用方法:
- attach_mock:將一個mock對象添加到另一個mock對象中。
- configure_mock,更改mock對象的return_value值。
- mock_add_spec:給mock對象添加新的屬性。
- reset_mock:將mock對象恢復到初始狀態。
acttach_mock acttach_mock
將一個mock對象添加到另一個mock對象中。

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg class Bar(object): def f2(self, *args): pass # 分別構造foo和bar的mock對象 mock_foo = Mock(spec=Foo) mock_bar = Mock(spec=Bar) # 打印也沒問題 print(mock_foo, mock_bar) # <Mock spec='Foo' id='57738096'> <Mock spec='Bar' id='130627728'> # 分別為兩個mock對象的方法添加返回值 mock_foo.f1.return_value = 'Foo.f1' mock_bar.f2.return_value = 'Bar.f2' # 正常的調用都沒問題 print(mock_foo.f1()) # Foo.f1 print(mock_bar.f2()) # Bar.f2 # 使用attach_mock將mock_bar對象添加到mock_foo中 mock_foo.attach_mock(mock_bar, 'bar') # 現在mock_bar對象成為了mock_foo mock對象的一個屬性bar print(mock_foo.bar) # <Mock name='mock.bar' spec='Bar' id='132987120'> # mock_foo.bar等於拿到了mock_bar對象,然后調用其中的f2方法,並且得到了之前賦值的返回值 print(mock_foo.bar.f2()) # Bar.f2
需要注意的是,attach_mock(self, mock, attribute)
必須為添加進來的mock對象指定一個屬性名。
configure_mock
configure_mock
用來更改mock對象的return_value值。

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg def f2(self, arg): return arg # 實例化mock對象並添加屬性和返回值 mock_obj = Mock(spec=Foo, return_value='abc') # 正常調用mock對象得到預期的結果 abc print(mock_obj()) # abc # 使用configure_mock修改mock對象的return_value值 mock_obj.configure_mock(return_value='xyz') # 修改成功 print(mock_obj()) # xyz # 可以批量設置返回值,比如f1方法的返回值為 '100', f2方法的返回值為 200 spec_dict = {'f1.return_value': '100', 'f2.return_value': 200} # 將字典打散后使用configure_mock設置到mock對象中 mock_obj.configure_mock(**spec_dict) print(mock_obj()) # xyz print(mock_obj.f1()) # 100 ps:字符串類型的100 print(mock_obj.f2()) # 2090
mock_add_spec mock_add_spec(self, spec, spec_set=False)
用來給mock對象添加一個新的屬性,新的屬性會覆蓋掉原來的屬性。spec_set
指屬性可讀可寫,默認是只讀,但可寫我沒測試出來....歡迎留言指正。

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg class Bar(object): def f2(self, args): return args def ace(): pass # 實例化mock對象 mock_obj = Mock(spec=Foo) print(mock_obj.f1()) # <Mock name='mock.f1()' id='119946576'> # 使用mock_add_spec給mock_obj添加一個新的屬性 mock_obj.mock_add_spec(Bar) print(mock_obj.f2()) # <Mock name='mock.f2()' id='46952912'> # 正常的使用都沒問題 mock_obj.f2.return_value = 'Bar.f2' print(mock_obj.f2()) # Bar.f2 # 上面添加的屬性是類,現在是函數,記得函數這里沒有方法,別瞎點啊 mock_obj.mock_add_spec(ace) mock_obj.return_value = 'function' print(mock_obj()) # function # 另外,新添加的屬性會覆蓋掉之前的屬性。現在的mock對象模擬的函數ace對象,ace函數哪有什么f1啊 print(mock_obj.f1()) # AttributeError: Mock object has no attribute 'f1'
reset_mock reset_mock
將mock對象回復到初識狀態,避免了重新構造mock對象帶來的開銷。

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg mock_obj = Mock(spec=Foo) mock_obj.f1() # 這里如果不使用 reset_mock, 那么f1方法就被調用了兩次,下面的 assert_called_once_with就會報錯,現在則不報錯了 mock_obj.reset_mock() mock_obj.f1() mock_obj.f1.assert_called_once_with()
mock統計方法
再來看mock關於統計的一些方法:
- called:跟蹤mock對象所做的任意調用的訪問器。
- mock_calls:顯示工廠調用和方法調用。
- call_args:mock對象的初始化參數。
- call_args_list:調用中使用參數。
- call_count:mock對象被調用次數。
- method_calls:以列表的形式返回mock對象都調用了哪些方法。
called

from unittest.mock import Mock def ace(): pass # 構造 mock對象並沒有調用 mock_obj = Mock(spec=ace) # OK,此時mock對象沒有調用,所以mock_obj.called:False print(mock_obj.called) # False # OK,現在調用了,那么 mock_obj.called:True mock_obj() print(mock_obj.called) # True
called
只要檢測到mock對象被調用,就返回True。
call_count

from unittest.mock import Mock def ace(): pass mock_obj = Mock(spec=ace) mock_obj() mock_obj() mock_obj() print(mock_obj.call_count) # 3
call_count
檢查mock對象被調用了多少次。
call_args && call_args_list

from unittest.mock import Mock mock_obj = Mock() mock_obj() print(mock_obj.call_args) # call() print(mock_obj.call_args_list) # [call()]
call_args_list
以列表的形式返回工廠調用時所有的參數。
method_calls

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg mock_obj = Mock(spec=Foo) mock_obj() mock_obj.f1() print(mock_obj.call_args) # call() print(mock_obj.call_args_list) # [call()] print(mock_obj.method_calls) # [call.f1()]
mock_calls

from unittest.mock import Mock class Foo(object): def f1(self, arg): return arg mock_obj = Mock(spec=Foo) mock_obj() mock_obj.f1() print(mock_obj.mock_calls) # [call(), call.f1()]
首先,mock對象被調用時,執行工廠call
方法,完事第二次調用了f1方法,所以mock_calls
返回了兩個方法。
[Mock測試概念介紹](https://www.jianshu.com/p/3944c0b82f30) | [如何 mock 數據](https://www.jianshu.com/p/63056120fab8) | [Python中的模塊學習之mock模塊](https://blog.csdn.net/peiyao456/article/details/77075173)