我以以下方式使用redis
:
from redis import Redis
redis_client = Redis()
def get_datetime_from_redis(key):
start_time = redis_client.get(key)
start_time = datetime.strptime(datetime_as_string, "%Y-%m-%d %H:%M:%S.%f")
duration = (datetime.now() - start_time).total_seconds()
return duration
我想用以下方式测试功能:
import pytest
from core.utils import get_datetime_from_redis
from unittest.mock import Mock
from datetime import datetime
def test_get_datetime_from_redis(monkeypatch):
mock_redis = Mock()
mock_redis.get.return_value = datetime(2022, 1, 26)
monkeypatch.setattr('core.utils.Redis', mock_redis)
get_datetime_from_redis('foo')
mock_redis.get.assert_called_once()
但问题是,当monkeypatch.setattr('core.utils.Redis', mock_redis)
运行时,redis_client
已经实例化,所以我不是在嘲笑正确的版本。
如何处理这些类型的测试?
它是一个全局变量这一事实不应该成为问题:monckeypatch可以应用于每个作用域,甚至现有实例。
这里的问题是你使用monckeypatch固定装置的方式。
由于函数使用core.utils
模块中的redis_clent
变量,因此monckeypatch
fixture应该模拟它,而不是Redis类本身。
因此:
import pytest
from core.utils import redis_client
from core.utils import get_datetime_from_redis
from datetime import datetime
def test_get_datetime_from_redis(monkeypatch):
redis_get_called = []
# define mock function
def mock_redis_get(self, k, default=None) :
get_called.append(1) # Add to call count
return datetime(2022, 1, 26)
# patch redis_client variable
monkeypatch.setattr(redis_client, 'get', mock_redis_get)
# act
get_datetime_from_redis('foo')
# assert
assert len(redis_get_called) == 1
现在只需注意Redisget
方法的签名:
在monckeypatch中,使用与模拟函数相同的签名是个好主意。。。这就是为什么这里的mock函数还有self
、k
(而不是key
(和default
参数(我认为最后一个参数可以忽略,因为它是一个可选参数(
不确定它是否能完全解决问题,但您应该将测试设计为由fixture
负责管理模拟redis(mock_redis
(的创建。通过将这些代码封装在fixture中,您将能够管理fixture
的创建时间(每个会话、每个模块、每个函数一次(,并在单个位置收集所有这些代码。下面是一个如何做到这一点的好例子。
第二点是在代码中实例化redis_client
客户端的方法。由于它不是在方法或类中完成的,所以一旦导入模块,就会创建redis_client
。您还应该考虑推迟实例化,将其放在一个方法或类中,以便在需要时调用。