我想測試一個不斷髮展SQLite數據庫應用程序,它是中使用的「生產性」平行。事實上,我正在調查一大堆文本文件,將它們導入數據庫並擺弄它。我習慣於開發測試驅動,我不想放棄這項調查。但對「生產」數據庫進行測試感覺有點奇怪。因此,我的目標是針對測試數據庫(一個真正的SQLite數據庫,而不是模擬)運行測試,其中包含一個受控但可觀的實際數據,顯示我在調查過程中遇到的各種變化。「嘲笑它在python模擬中的定義」?
爲了支持這種做法,我有一個包含一個函數返回時使用像這樣的數據庫的名稱中央模塊myconst.py
:
import myconst
conn = sqlite3.connect(myconst.get_db_path())
現在在unittest
TestCase
S,我想到了嘲諷,像這樣:
@patch("myconst.get_db_name", return_value="../test.sqlite")
def test_this_and_that(self, mo):
...
當測試要求,將在嵌套函數,訪問使用myconst.get_db_path()
的數據庫功能。
我試圖先做一個小嘲弄for myself,但它往往笨拙和容易出錯,所以我決定跳入python mock
模塊,如前所示。
不幸的是,我發現warningsallover,「使用它的模擬,而不是在那裏的定義」,我應該像這樣:
@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
的行爲和它未能讓我嘲笑「源頭」。由於多模塊設置笨拙在這裏,爲了大家的方便,我提供了原始代碼also on 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
。從這個例子中,我可以得出結論,我的方法是安全的,但在另一方面,警告是橫跨相當長的一段源一致,我不想使用概念我不grok硬拼冒險我「生產」數據庫。
所以我的問題是:難道我誤解了警告或只是過於謹慎的這些警告?
我很懷疑你會從這裏嘲笑中獲得更多價值。當你需要測試一個對象*接收到一個消息*(通常是某種修改狀態命令)時,嘲笑是有用的。您正在函數的中間獲取配置數據。這對於完整功能的測試更有利於開始和結束。對於將數據從一個存儲引擎遷移到另一個存儲引擎的工具,我將單元測試的唯一東西是您在代碼中執行的實際數據轉換(即不是SQL)。我會確保邏輯純粹是基於輸入/輸出的,無需模擬。 – jpmc26
其實我想這樣做。但是轉換會爲數據庫產生「副內容」(例如新發現的主數據)並立即將其更新到數據庫。這看起來很奇怪,但它使代碼易於理解,所以我不想重構這個。然而,在其他重構和增強過程中,我想要回歸。所以我產生了嘲笑配置的想法。 – flaschbier
我發現更多的東西是,將「什麼」與「何時」分開是非常非常有用的。這意味着您可以擁有執行轉換並返回數據的函數/類,然後執行其他函數來執行更新。如果你不想改變你的代碼的組織,我實際上會建議去完整的集成/功能/驗收測試路線。而不是模擬配置,切換到實際的配置文件。在測試時使用包含測試環境參數的配置文件。 – jpmc26