2016-03-17 20 views
1

我想測試一個不斷髮展SQLite數據庫應用程序,它是中使用的「生產性」平行。事實上,我正在調查一大堆文本文件,將它們導入數據庫並擺弄它。我習慣於開發測試驅動,我不想放棄這項調查。但對「生產」數據庫進行測試感覺有點奇怪。因此,我的目標是針對測試數據庫(一個真正的SQLite數據庫,而不是模擬)運行測試,其中包含一個受控但可觀的實際數據,顯示我在調查過程中遇到的各種變化。「嘲笑它在python模擬中的定義」?

爲了支持這種做法,我有一個包含一個函數返回時使用像這樣的數據庫的名稱中央模塊myconst.py

import myconst 
conn = sqlite3.connect(myconst.get_db_path()) 

現在在unittestTestCase 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硬拼冒險我「生產」數據庫。

所以我的問題是:難道我誤解了警告或只是過於謹慎的這些警告?

+0

我很懷疑你會從這裏嘲笑中獲得更多價值。當你需要測試一個對象*接收到一個消息*(通常是某種修改狀態命令)時,嘲笑是有用的。您正在函數的中間獲取配置數據。這對於完整功能的測試更有利於開始和結束。對於將數據從一個存儲引擎遷移到另一個存儲引擎的工具,我將單元測試的唯一東西是您在代碼中執行的實際數據轉換(即不是SQL)。我會確保邏輯純粹是基於輸入/輸出的,無需模擬。 – jpmc26

+0

其實我想這樣做。但是轉換會爲數據庫產生「副內容」(例如新發現的主數據)並立即將其更新到數據庫。這看起來很奇怪,但它使代碼易於理解,所以我不想重構這個。然而,在其他重構和增強過程中,我想要回歸。所以我產生了嘲笑配置的想法。 – flaschbier

+0

我發現更多的東西是,將「什麼」與「何時」分開是非常非常有用的。這意味着您可以擁有執行轉換並返回數據的函數/類,然後執行其他函數來執行更新。如果你不想改變你的代碼的組織,我實際上會建議去完整的集成/功能/驗收測試路線。而不是模擬配置,切換到實際的配置文件。在測試時使用包含測試環境參數的配置文件。 – jpmc26

回答

1

最後我整理出來。也許這將有助於未來的遊客,所以我將分享我的發現:

當改變代碼like so

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將不會替代引用的對象,這兩個函數都是模塊myconst中的函數get_db_namemock將代替名稱的使用"myconst.get_db_name"Mock對象作爲第二個參數傳遞給測試。

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我很安全。人們可以爭論這是否是好風格(假設是後者),但從技術上講它是安全的。我是否會嘲笑庫函數,我幾乎無法控制對該函數的訪問,因此嘲笑「定義的位置」變得有風險。這就是爲什麼引用的來源是警告。