使用Python模拟类编写函数和属性

4

我在尝试给一个类打补丁,这个类包含一个函数和一个属性。

我所使用的项目结构如下:

project
|- src
|  |- logic
|  |  |- sub_logic
|  |  |  | __init__.py
|  |  |  | cache.py
|  |  |  | manager.py
|  |  | __init__.py  
|- test
|  | test.py  

我的缓存文件长这样

class Cache(object):
    def __init__(self, val):
        self._val = val

    @property
    def Val(self):
        return self._val

    def other_function(self):
        return False

管理文件看起来像这样
from cache import Cache


class Manager(object):
    def __init__(self):
        self._cache = Cache(20)

    def do_something(self):
        if self._cache.Val != 20:
            raise ValueError(u"Val is not 20")

        return True

    def do_something_else(self):
        if self._cache.other_function():
            raise ValueError(u"Something is True")

我尝试过以下测试:

1.

2.

3.

from unittest import TestCase
from mock import PropertyMock, patch

from logic.sub_logic.manager import Manager
from logic.sub_logic.cache import Cache


class ManagerTestCase(TestCase):

    def test_01_cache(self):
        manager = Manager()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache.Val', new_callable=PropertyMock)
    def test_02_cache(self, property_mock):
        property_mock.return_value = 20
        manager = Manager()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_03_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_04_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        cache_mock.Val = PropertyMock()
        cache_mock.Val.return_value = 20

        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache.Val', new_callable=PropertyMock)
    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_05_cache(self, cache_mock, property_mock):
        cache_mock.other_function.return_value = True
        property_mock.return_value = 20
        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

    @patch('logic.sub_logic.manager.Cache', spec=Cache)
    def test_06_cache(self, cache_mock):
        cache_mock.other_function.return_value = True
        cache_mock.Val = 20

        manager = Manager()
        with self.assertRaises(ValueError):
            manager.do_something_else()
        self.assertEqual(manager.do_something(), True)

问题在于test_04_cache和test_05_cache无法工作。 在调试测试时,提供的模拟参数与我预期的相同。但是Manager创建了一个MagicMock,其中属性Val不是PropertyMock而是另一个MagicMock。
我在PyCharm调试器中检查了test_06_cache,报告如下:
cache_mock.Val = {int}20 manager._cache.Val = {MagicMock}<MagicMock name='Cache().Val' id='61044848'>
我有遗漏吗?还是这不可能?

在test_05中,首先你对Cache.Val进行了模拟,然后又对整个Cache对象进行了模拟,用MagicMock替换了整个对象。这就是为什么在test_05中,你会看到Val被替换成了MagicMock。 - marxin
我扩展了我的问题,其中包括了您的建议。单元测试仍然失败,PyCharm调试器告诉我cache_mock和manager实例之间存在差异。 - KlemensE
1
你需要设置 cache_mock().Val - 请注意,你可以访问类而不是实例。 - jonrsharpe
非常感谢!问题已经解决了。 - KlemensE
谢谢!在Python2中,使用mock.StubOutWithMock(Model, 'func')非常简单。但是,在Python3中,这方面有了很大的变化,并且相关文档很难找到。您上面的示例让我理解了4年之久的问题。 - MagicLAMP
显示剩余3条评论
1个回答

6

当你使用时

@patch('logic.sub_logic.manager.Cache', spec=Cache)

生成的模拟对象是为了该类而创建的。您的Manager通过在__init__中调用该类来创建实例。因此,您应该在mock_cache()(注意括号)上设置属性和返回值,这是将分配给manager._cache的“实例”,而不是“类”mock_cache。
请注意,由于管理器不知道缓存正在使用@property,因此您可以只设置:
mock_cache().Val = 20

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接