Pytest:如何测试包含全局变量的函数



我以以下方式使用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变量,因此monckeypatchfixture应该模拟它,而不是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函数还有selfk(而不是key(和default参数(我认为最后一个参数可以忽略,因为它是一个可选参数(

不确定它是否能完全解决问题,但您应该将测试设计为由fixture负责管理模拟redis(mock_redis(的创建。通过将这些代码封装在fixture中,您将能够管理fixture的创建时间(每个会话、每个模块、每个函数一次(,并在单个位置收集所有这些代码。下面是一个如何做到这一点的好例子。

第二点是在代码中实例化redis_client客户端的方法。由于它不是在方法或类中完成的,所以一旦导入模块,就会创建redis_client。您还应该考虑推迟实例化,将其放在一个方法或类中,以便在需要时调用。

最新更新