如何使用mock作为函数param修补Python中的常量

2024-05-15 08:38:36 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图理解在Python中使用mock.patch修补常量的不同方法。 我的目标是能够使用在测试类中定义的变量作为常量的修补值。

我发现这个问题解释了如何修补常数: 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

测试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

我想我不应该使用返回值,因为模拟常数不是函数。那么在调用常量时,我应该使用什么属性来替换返回的值呢?


Tags: 方法testimportselfgetmydefunittest
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'

所以根本不需要修补常数

我认为您正在尝试了解单元测试、模拟对象以及如何替换被测代码中常量的值。

我将从您关于修补常量的特定问题开始,然后描述替换常量值的更一般的方法。

你的具体问题是关于patch("mymodule.MY_CONSTANT", 3)patch("mymodule.MY_CONSTANT")之间的区别。根据docs,第二个参数是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)

随着代码变得更加复杂,这可能会变得更加痛苦。如果必须通过一堆层传递该覆盖,则可能不值得。但是,这可能表明您的设计有一个问题,使代码更难测试。

相关问题 更多 >