【问题标题】:python mock global function that is used in class类中使用的python模拟全局函数
【发布时间】:2015-08-13 09:30:35
【问题描述】:

我似乎无法在 Python 中模拟。我有一个全局函数:

a.py:

def has_permission(args):
    ret_val = ...get-true-or-false...
    return ret_val

b.py:

class MySerializer(HyperlinkedModelSerializer):

     def get_fields():
         fields = super().get_fields()
         for f in :
             if has_permission(...):
                 ret_val[f.name] = fields[f]
         return ret_val

c.py:

class CountrySerializer(MySerializer):
    class Meta:
        model = Country

问题:现在我想测试 c.py,但我想模拟 a.py 中定义但在 get_fields 中调用的 has_permission 函数-b.py 中定义的 MySerializer 类的方法...我该怎么做?

我尝试过类似的方法:

@patch('b.MySerializer.has_permission')

@patch('b.MySerializer.get_fields.has_permission')

@patch('a.has_permission')

但是我尝试的一切要么不起作用,has_permission 仍在执行,要么 python 抱怨它找不到属性'has_permission'

修补完成:

test.py

class TestSerializerFields(TestCase):
    @patch(... the above examples....)
    def test_my_country_serializer():
        s = CountrySerializer()
        self..assertTrue(issubclass(my_serializer_fields.MyCharField, type(s.get_fields()['field1'])))

【问题讨论】:

  • 你在哪里申请patch
  • @vks:大概是在测试中。
  • @patch('b.MySerializer.has_permission') 是错误的,因为has_permission 函数不属于该类。你传递给patch 的是一个导入路径。微妙之处在于,虽然在a.py, once you've imported it in b.py` 中定义,但它也可以从b.py 导入......而且它是在b.py 中导入的副本,您要修补

标签: python unit-testing mocking


【解决方案1】:

您需要在b 模块中修补全局:

@patch('b.has_permission')

因为那是您的代码寻找它的地方。

另请参阅mock 文档中的Where to patch section

【讨论】:

  • Tnx...就是这样!如果我理解正确,那么 patch('b.has_permission') 会修补对整个 b.py 文件中定义的 anywhere 的所有调用 has_permission?
  • @RvL:它替换了您导入模块的has_permission 对象,是的。所有使用has_permission 的代码总是使用同一个全局对象。
  • @RvL:你不能只为一个函数修补全局变量的使用。
  • 啊,这对我来说开始有意义了。 Tnx :)
【解决方案2】:

您需要修补测试运行时存在的方法。如果您在测试代码已经导入后尝试修补定义它的方法,则补丁将不起作用。在@patch(...)执行时,被测代码已经将全局方法抓取到自己的模块中。

这是一个例子:

app/util/config.py:

# This is the global method we want to mock
def is_search_enabled():
    return True

app/service/searcher.py:

# Here is where that global method will be imported 
#  when this file is first imported
from app.util.config import is_search_enabled

class Searcher:
    def __init__(self, api_service):
        self._api_service = api_service

    def search(self):
        if not is_search_enabled():
            return None
        return self._api_service.perform_request('/search')

test/service/test_searcher.py:

from unittest.mock import patch, Mock
# The next line will cause the imports of `searcher.py` to execute...
from app.service.searcher import Searcher
# At this point, searcher.py has imported is_search_enabled into its module.
# If you later try and patch the method at its definition 
#  (app.util.config.is_search_enabled), it will have no effect because 
#  searcher.py won't look there again.

class MockApiService:
    pass

class TestSearcher:

    # By the time this executes, `is_search_enabled` has already been
    #  imported into `app.service.searcher`.  So that is where we must
    #  patch it.
    @patch('app.service.searcher.is_search_enabled')
    def test_no_search_when_disabled(self, mock_is_search_enabled):
        mock_is_search_enabled.return_value = False
        mock_api_service = MockApiService()
        mock_api_service.perform_request = Mock()
        searcher = Searcher(mock_api_service)

        results = searcher.search()

        assert results is None
        mock_api_service.perform_request.assert_not_called()

    # (For completeness' sake, make sure the code actually works when search is enabled...)
    def test_search(self):
        mock_api_service = MockApiService()
        mock_api_service.perform_request = mock_perform_request = Mock()
        searcher = Searcher(mock_api_service)
        expected_results = [1, 2, 3]
        mock_perform_request.return_value = expected_results

        actual_results = searcher.search()

        assert actual_results == expected_results
        mock_api_service.perform_request.assert_called_once_with('/search')

【讨论】:

    猜你喜欢
    • 2022-01-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-08-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多