簡體   English   中英

在python模擬中“嘲笑它的定義”?

[英]"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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM