【问题标题】:How do I mock the python decorator argument?如何模拟 python 装饰器参数?
【发布时间】:2021-03-20 20:24:33
【问题描述】:

如何使用 cachetools 在 Python 单元测试中模拟缓存值?我写了一个名为 cache_controller 的装饰器。我有正当理由不使用 Cachetools 的装饰器。 当我在编写测试代码时尝试模拟 TTLCache 对象时,我无法模拟它。会是什么原因?

src/helpers/cache.py

from cachetools import TTLCache

total_cache = TTLCache(maxsize=1024, ttl=600)

src/wrappers/cache_controller.py

from functools import wraps

def cache_controller(cache, cache_args: tuple):
    """
    :return: wrapper
    """

    def decorator(func):
        @wraps(func)
        def wrapper(self, *args, **kwargs):
            print("Cache in CACHE CONROLLER:", cache)
            cache_key = tuple([kwargs[arg] for arg in cache_args])
            print("Cache key in CACHE CONROLLER:", cache_key)
            cached_value = cache.get(cache_key)
            print("Cache value in CACHE CONROLLER:", cached_value)
            
            if cached_value:
                print(f"{self.__class__.__name__} : {cache_key} : from Local Cache : {cached_value}")
                return cached_value
            
            result = func(self, *args, **kwargs)

            cache.update({cache_key: result})
            print(f"{self.__class__.__name__} : {cache_key} : Updated to Local Cache : {result}")

            return result

        return wrapper

    return decorator

src.run.py

from .wrappers.cache_controller import cache_controller
from .helpers.cache import total_cache


class ExampleClass(object):

    @cache_controller(
        cache=total_cache,
        cache_args=("a", "b")
    )
    def example_method(self, a: int, b: int):
        return a+b

*test/test_cache_controller.py

import unittest
from cachetools.ttl import TTLCache
from src.run import ExampleClass
from unittest import TestCase, mock


class TestCacheController(TestCase):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)

    def setUp(self):
        self.fake_cache = TTLCache(maxsize=10, ttl=3600)
        self.fake_cache.__setitem__((5, 6), 1)
        self._class = ExampleClass()

    def tearDown(self):
        self._class = None

    @ mock.patch("src.run.total_cache")
    def test_if_cache_filled(self, mock_total_cache):
        mock_total_cache.return_value = self.fake_cache

        result = self._class.example_method(a=5, b=6)
        func_expected = 11
        cache_expected = 1
        print("Result:", result)

        self.assertEqual(result, cache_expected)


if __name__ == '__main__':
    unittest.main()

.. 并运行测试代码

cachetools-mock ❯ python -m test.test_cache_controller                                                                                                                                     

Cache in CACHE CONROLLER: TTLCache([], maxsize=1024, currsize=0)
Cache key in CACHE CONROLLER: (5, 6)
Cache value in CACHE CONROLLER: None

ExampleClass : (5, 6) : Updated to Local Cache : 11
Result: 11
F
======================================================================
FAIL: test_if_cache_filled (__main__.TestCacheController)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "/Users/sumeyyeozkaynak/.pyenv/versions/3.7.5/lib/python3.7/unittest/mock.py", line 1255, in patched
    return func(*args, **keywargs)
  File "/Users/sumeyyeozkaynak/Desktop/.optiwisdom/workspace/cachetools-mock/test/test_cache_controller.py", line 28, in test_if_cache_filled
    self.assertEqual(result, cache_expected)
AssertionError: 11 != 1

----------------------------------------------------------------------
Ran 1 test in 0.004s

FAILED (failures=1)

【问题讨论】:

    标签: caching mocking wrapper python-unittest python-decorators


    【解决方案1】:

    我看到很多回复说装饰器得到的参数是不可模拟的。这是正确的,因为当对象被模拟后正在测试时,再次调用该类并覆盖模拟的对象。

    如果在被测单元测试方法中导入了import ExampleClass,问题就解决了。

    import unittest
    from unittest import TestCase, mock
    
    class TestCacheController(TestCase):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        @mock.patch.dict("src.helpers.cache.total_cache", {(5, 6): 1})
        def test_if_cache_filled(self):
            from src.run import ExampleClass
            result = ExampleClass().example_method(a=5, b=6)
            print("Result:", result)
    
            self.assertEqual(result, 1)
    
    
    if __name__ == '__main__':
        unittest.main()
    

    【讨论】:

    • @mock.patch.dict("src.run.total_cache", {(5, 6): 1})路径可以是这样的。两者都在工作。
    猜你喜欢
    • 1970-01-01
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-21
    • 2013-11-03
    • 2014-07-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多