![](/img/trans.png)
[英]Python Mocking - How to mock Google's storage.client?
[英]"Mocking where it's defined" in python mock?
我想測試一個不斷發展的 SQLite 數據庫應用程序,該應用程序並行使用“富有成效”。 事實上,我正在通過將它們導入數據庫並擺弄它來調查一堆大型文本文件。 我習慣於開發測試驅動,我不想在這次調查中放棄它。 但是對“生產”數據庫運行測試感覺有些奇怪。 因此,我的目標是針對包含受控但大量真實數據的測試數據庫(一個真正的 SQLite 數據庫,而不是模擬數據庫)運行測試,這些數據顯示了我在調查期間遇到的各種可變性。
為了支持這種方法,我有一個中央模塊myconst.py
其中包含一個返回數據庫名稱的函數,使用方式如下:
import myconst
conn = sqlite3.connect(myconst.get_db_path())
現在在unittest
TestCase
,我想像這樣模擬:
@patch("myconst.get_db_name", return_value="../test.sqlite")
def test_this_and_that(self, mo):
...
測試調用的函數將在嵌套函數中使用myconst.get_db_path()
訪問數據庫。
我曾嘗試先對自己進行一些mock
,但它往往很笨拙且容易出錯,因此我決定深入研究之前所示的 python mock
模塊。
不幸的是,我發現警告所有在“使用它的模擬,而不是在那里的定義”,我應該像這樣:
@patch("mymodule.myconst.get_db_name", return_value="../test.sqlite")
def test_this_and_that(self, mo):
self.assertEqual(mymodule.func_to_be_tested(), 1)
但是mymodule
可能不會自己調用數據庫函數,而是將其委托給另一個模塊。 這反過來意味着我的單元測試必須知道實際訪問數據庫的調用樹——我真的想避免這一點,因為在重構代碼時會導致不必要的測試重構。
所以我試圖創建一個最小的例子來理解mock
的行為以及它無法讓我“在源頭”模擬的地方。 因為這里的多模塊設置很笨拙,為了方便大家,我也在github上提供了原始代碼。 看到這個:
myconst.py
----------
# global definition of the database name
def get_db_name():
return "../production.sqlite"
# this will replace get_db_name()
TEST_VALUE = "../test.sqlite"
def fun():
return TEST_VALUE
inner.py
--------
import myconst
def functio():
return myconst.get_db_name()
print "inner:", functio()
test_inner.py
-------------
from mock import patch
import unittest
import myconst, inner
class Tests(unittest.TestCase):
@patch("inner.myconst.get_db_name", side_effect=myconst.fun)
def test_inner(self, mo):
"""mocking where used"""
self.assertEqual(inner.functio(), myconst.TEST_VALUE)
self.assertTrue(mo.called)
outer.py
--------
import inner
def functio():
return inner.functio()
print "outer:", functio()
test_outer.py
-------------
from mock import patch
import unittest
import myconst, outer
class Tests(unittest.TestCase):
@patch("myconst.get_db_name", side_effect=myconst.fun)
def test_outer(self, mo):
"""mocking where it comes from"""
self.assertEqual(outer.functio(), myconst.TEST_VALUE)
self.assertTrue(mo.called)
unittests.py
------------
"""Deeply mocking a database name..."""
import unittest
print(__doc__)
suite = unittest.TestLoader().discover('.', pattern='test_*.py')
unittest.TextTestRunner(verbosity=2).run(suite)
test_inner.py
就像上面鏈接的來源一樣工作,所以我期待它通過。 當我正確理解警告時, test_outer.py
應該會失敗。 但是所有的測試都毫無怨言地通過了! 所以我的模擬一直被繪制,即使模擬函數是從調用堆棧中調用的,就像在test_outer.py
。 從這個例子中,我可以得出結論,我的方法是安全的,但在另一方面,警告是橫跨相當長的一段源一致的,我不希望使用的概念,我不硬拼冒險我“生產”數據庫神交。
所以我的問題是:我是誤解了警告還是這些警告過於謹慎?
最后我整理了一下。 也許這會對未來的訪問者有所幫助,所以我將分享我的發現:
當像這樣更改代碼時:
inner.py
--------
from myconst import get_db_name
def functio():
return get_db_name()
test_inner.py
-------------
@patch("inner.get_db_name", side_effect=myconst.fun)
def test_inner(self, mo):
self.assertEqual(inner.functio(), myconst.TEST_VALUE)
test_inner
會成功,但test_outer
會中斷
AssertionError: '../production.sqlite' != '../test.sqlite'
這是因為mock.patch
不會替換引用的object ,在這兩種情況下都是模塊myconst
中的函數get_db_name
。 mock
將使用作為第二個參數傳遞給測試的Mock
對象替換名稱"myconst.get_db_name"
的用法。
test_outer.py
-------------
@patch("myconst.get_db_name", side_effect=myconst.fun)
def test_outer(self, mo):
self.assertEqual(outer.functio(), myconst.TEST_VALUE)
因為我只是嘲笑"myconst.getdb_name"
這里inner.py
訪問get_db_name
通過"inner.get_db_name"
,會導致測試失敗。
但是,通過使用正確的名稱,可以解決此問題:
@patch("outer.inner.get_db_name", return_value=myconst.TEST_VALUE)
def test_outer(self, mo):
self.assertEqual(outer.functio(), myconst.TEST_VALUE)
所以結論是,當我確保訪問數據庫的所有模塊都包含myconst
並使用myconst.get_db_name
時,我的方法將是安全的。 或者,所有模塊都可以from myconst import get_db_name
並使用get_db_name
。 但我必須在全球范圍內做出這個決定。
因為我控制所有訪問get_db_name
代碼我是安全的。 人們可以爭論這是否是好的風格(假設是后者),但從技術上講它是安全的。 我會模擬一個庫函數嗎,我幾乎無法控制對該函數的訪問,因此模擬“它的定義位置”變得有風險。 這就是為什么引用的消息來源是警告的原因。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.