簡體   English   中英

Python 模擬:mocking 基礎 class 為 inheritance

[英]Python mock: mocking base class for inheritance

我正在測試一個 class,它從另一個非常復雜的繼承而來,具有 DB 連接方法和一堆依賴項。 我想模擬它的基數 class 以便我可以很好地使用子類中定義的方法,但是在我從模擬的 class 繼承的那一刻,object 本身變成了一個模擬並丟失了它的所有方法。

我如何模擬超類?

大致情況可以概括為:

import mock

ClassMock = mock.MagicMock()

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

這是一個簡化的案例。 實際發生的是RealClass擴展AnotherClass ,但我設法攔截了AnotherClass並將其替換為模擬。

這是我一直在努力解決的問題,但我想我終於找到了解決方案。

正如您已經注意到的,如果您嘗試用 Mock 替換基類,則您嘗試測試的類只會變成模擬,這會破壞您測試它的能力。 解決方案是只模擬基類的方法而不是整個基類本身,但這說起來容易做起來難:在逐個測試的基礎上一個一個地模擬每個方法可能很容易出錯。

我所做的是創建一個類來掃描另一個類,並將與另一個類上的方法匹配的Mock()分配給自己。 然后,您可以在測試中使用此類代替真正的基類。

這是假的類:

class Fake(object):
    """Create Mock()ed methods that match another class's methods."""

    @classmethod
    def imitate(cls, *others):
        for other in others:
            for name in other.__dict__:
                try:
                    setattr(cls, name, Mock())
                except (TypeError, AttributeError):
                    pass
        return cls

因此,例如您可能有一些這樣的代碼(抱歉,這有點做作,假設BaseClassSecondClass正在做重要的工作並且包含許多方法,甚至根本不需要由您定義):

class BaseClass:
    def do_expensive_calculation(self):
        return 5 + 5

class SecondClass:
    def do_second_calculation(self):
        return 2 * 2

class MyClass(BaseClass, SecondClass):
    def my_calculation(self):
        return self.do_expensive_calculation(), self.do_second_calculation()

然后你就可以寫一些這樣的測試:

class MyTestCase(unittest.TestCase):
    def setUp(self):
        MyClass.__bases__ = (Fake.imitate(BaseClass, SecondBase),)

    def test_my_methods_only(self):
        myclass = MyClass()
        self.assertEqual(myclass.my_calculation(), (
            myclass.do_expensive_calculation.return_value, 
            myclass.do_second_calculation.return_value,
        ))
        myclass.do_expensive_calculation.assert_called_once_with()
        myclass.do_second_calculation.assert_called_once_with()

因此,基類中存在的方法仍可用作您可以與之交互的模擬,但您的類本身不會成為模擬。

我一直很小心地確保這在 python2 和 python3 中都有效。

這應該對你有用。

import mock

ClassMock = mock.MagicMock # <-- Note the removed brackets '()'

class RealClass(ClassMock):

    def lol(self):
        print 'lol'

real = RealClass()
real.lol()  # Does not print lol, but returns another mock

print real # prints <MagicMock id='...'>

你不應該像你那樣傳遞類的實例。 mock.MagicMock是一個類,所以你直接傳遞它。

In [2]: inspect.isclass(mock.MagicMock)
Out[2]: True

我遇到了類似的問題,並且能夠通過@patch.object做到這一點。 請參閱官方 python 文檔中補丁裝飾器的示例。

class MyTest(unittest.TestCase):
    @patch.object(SomeClass, 'inherited_method')
    def test_something(self, mock_method):
        SomeClass.static_method()
        mock_method.assert_called_with()

只是舉例說明@Akash的回答,這實際上解決了我的 inheritance 模擬挑戰:

 @patch.object(SomeClassInheritingAnother, "inherited_method")
    def test_should_test_something(self, mocked_inherited_method, mocker, caplog):
       #Mocking an HTTP result status code
       type(mocked_inherited_method.return_value).status_code = mocker.PropertyMock(return_value=200)

       #Calling the inherited method, that should end up using the mocked method
       SomeClassInheritingAnother.inherited_method()

       #Considering that the request result is being logged as 'Request result: {response.status_code}'
       assert "Request result: 200" in caplog.text 

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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