我正在尝试了解使用 mock.patch 在Python中修补常量的不同方法。 我的目标是能够使用我的Test类中定义的变量作为我的常量的修补值。
我发现这个问题解释了如何修补常数: How to patch a constant in python 这个问题解释了如何在补丁中使用self: using self in python @patch decorator
但是从第二个链接开始,我无法使 testTwo 方式(将mock作为函数参数提供)工作
这是我的简化用例:
mymodule.py
MY_CONSTANT = 5
def get_constant():
return MY_CONSTANT
test_mymodule.py
import unittest
from unittest.mock import patch
import mymodule
class Test(unittest.TestCase):
#This works
@patch("mymodule.MY_CONSTANT", 3)
def test_get_constant_1(self):
self.assertEqual(mymodule.get_constant(), 3)
#This also works
def test_get_constant_2(self):
with patch("mymodule.MY_CONSTANT", 3):
self.assertEqual(mymodule.get_constant(), 3)
#But this doesn't
@patch("mymodule.MY_CONSTANT")
def test_get_constant_3(self, mock_MY_CONSTANT):
mock_MY_CONSTANT.return_value = 3
self.assertEqual(mymodule.get_constant(), 3)
#AssertionError: <MagicMock name='MY_CONSTANT' id='64980808'> != 3
我的猜测是我不应该使用 return_value ,因为 mock_MY_CONSTANT 不是函数。那么我应该使用什么属性来替换调用常量时返回的值?
答案 0 :(得分:5)
我认为您正在尝试学习单元测试,模拟对象以及如何替换被测代码中常量的值。
我将从您关于修补常量的具体问题开始,然后再介绍一种更通用的替换常量值的方法。
您的特定问题是关于patch("mymodule.MY_CONSTANT", 3)
和patch("mymodule.MY_CONSTANT")
之间的区别。根据{{3}},第二个参数是 new ,它包含将修补的替换值。如果将其保留为默认值,则MagicMock
对象正如您在问题中指出的那样,MagicMock.return_value
对于函数很有效,但是您没有调用MY_CONSTANT
,因此返回值永远不会被使用。
我对该问题的简短回答是:“不要使用MagicMock
来替换常量。”如果出于某种原因,您迫切希望这样做,则可以覆盖在该常量上调用的唯一内容,即其__eq__()
方法。 (我不认为这是个好主意。)
import unittest
from unittest.mock import patch
import mymodule
class Test(unittest.TestCase):
#This works
@patch("mymodule.MY_CONSTANT", 3)
def test_get_constant_1(self):
self.assertEqual(mymodule.get_constant(), 3)
#This also works
def test_get_constant_2(self):
with patch("mymodule.MY_CONSTANT", 3):
self.assertEqual(mymodule.get_constant(), 3)
#This now "works", but it's a horrible idea!
@patch("mymodule.MY_CONSTANT")
def test_get_constant_3(self, mock_MY_CONSTANT):
mock_MY_CONSTANT.__eq__ = lambda self, other: other == 3
self.assertEqual(mymodule.get_constant(), 3)
现在问更一般的问题。我认为最简单的方法不是更改常量,而是提供一种覆盖常量的方法。更改常数对我来说是错误的,因为它称为常数。 (当然,这只是一个约定,因为Python不强制执行常量值。)
这就是我要处理您尝试做的事情。
MY_CONSTANT = 5
def get_constant(override=MY_CONSTANT):
return override
然后,您的常规代码可以仅调用get_constant()
,而您的测试代码可以提供替代。
import unittest
import mymodule
class Test(unittest.TestCase):
def test_get_constant(self):
self.assertEqual(mymodule.get_constant(override=3), 3)
随着代码变得更加复杂,这可能会变得更加痛苦。如果您必须通过一堆图层传递该替代,则可能不值得。但是,也许这说明您的设计存在问题,使代码难以测试。
答案 1 :(得分:-2)
你可以在每个断言之前简单地将模拟值赋值给常量:
def test_get_constant_3(self):
mymodule.MY_CONSTANT = 3
self.assertEqual(mymodule.get_constant(), 3)
mymodule.MY_CONSTANT = 7
self.assertEqual(mymodule.get_constant(), 7)
另一个例子
# --- config.py ---
class AppConf:
APP_TIMEZONE = os.environ.get['APP_TIMEZONE']
# --- my_mod.py ---
from datetime import datetime
from config import AppConf
LOCAL_TZ = AppConf.APP_TIMEZONE
def to_local_tz(dt_obj, tz):
"""Return datetime obj for specific timezone"""
# some code here
return local_dt_obj
def get_local_time():
return to_local_tz(datetime.utcnow(), LOCAL_TZ).strftime('%H:%M')
# --- test_my_mod.py ---
import my_mod
class TestMyMod(unittest.TestCase):
@patch('my_mod.datetime')
def test_get_local_time(self, mock_dt):
# Mock to 15:00 UTC
mock_dt.utcnow.return_value = datetime(2017, 5, 3, 15)
# Test with TZ 'Europe/Kiev' +02:00 +03:00(DST)
my_mod.LOCAL_TZ = 'Europe/Kiev'
assert my_mod.get_local_time() == '18:00'
# Test with TZ 'America/New_York' -05:00 -04:00(DST)
my_mod.LOCAL_TZ = 'America/New_York'
assert my_mod.get_local_time() == '11:00'
所以根本不需要修补常量
答案 2 :(得分:-2)
为了在理论上回答这个问题,我认为你需要在常量上修补__int__()
方法,如下所示:
@patch("mymodule.MY_CONSTANT.__int__")
def test_get_constant_4(self, mock_MY_CONSTANT):
mock_MY_CONSTANT.return_value = 3
self.assertEqual(mymodule.get_constant(), 3)
显然,这是错误的做法,正如它所引发的错误所示。
AttributeError: 'int' object attribute '__int__' is read-only