【问题标题】:How to mock using patch relative paths?如何使用补丁相对路径进行模拟?
【发布时间】:2012-12-29 18:50:22
【问题描述】:

我在 python 测试文件中有这样的东西:

from mock import patch,
from ..monkey import ook
[...]
@patch('monkey.ook', Mock(return_value=None))
def test_run_ook (self, mock_ook):
    self.assertIsNone(ook())
    mock_ook.run.assert_called_once_with('')

当我运行这个测试时,我得到一个ImportError: No module named monkey。显然,我正在修补的路径不正确。但是,我不确定如何在不弄乱sys.pathPYTHONPATH 的情况下正确处理。

任何指针?

【问题讨论】:

    标签: python unit-testing mocking


    【解决方案1】:

    我使用了 Dan Passaro 的解决方案,直到我遇到了这个使用 patch.object 的解决方案——这对我来说看起来更好:

    from unittest.mock import patch,
    from .. import monkey
    [...]
    @patch.object(monkey, 'ook', Mock(return_value=None))
    def test_run_ook (self, mock_ook):
        self.assertIsNone(monkey.ook())
        mock_ook.run.assert_called_once_with('')
    

    优点:

    • 不需要样板代码 __name__ + '.object_to_be_mocked'
    • 测试用例的所有依赖项都在文件开头明确说明为import 语句。
    • 如果您尝试模拟的对象的虚线名称较长(例如“amazon.jungle.monkey.ook”)并且您因此编写@patch.object(amazon.jungle.monkey, 'ook', …),您的IDE 的静态代码分析可以确保至少amazon.jungle.monkey 是一个有效变量,因为您没有将整个内容写成字符串'amazon.jungle.monkey.ook'

    缺点:

    • 你不能做from ..monkey import ook,但需要做from .. import monkey并通过monkey访问ook,即monkey.ook。如果我需要经常写这个,为了方便起见,我会在测试的开头添加ook = monkey.ook。 (或者甚至是导入语句,以防我永远不需要模拟 monkey 的这个特定属性。)

    【讨论】:

    • 可能有几个缺点,但总的来说,在很多情况下它是非常有用的方法!谢谢!
    【解决方案2】:

    根据我收集到的信息,使用模拟,您需要在修补时提供一个带点的名称。幸运的是,每个模块都可以访问一个特殊的模块级变量__name__,其中包含模块的名称。使用它,如果您想修补模块本地的变量,您应该能够执行以下操作:

    import mock
    import unittest
    
    ook = lambda: "the ook"
    
    
    class OokTest(unittest.TestCase):
    
        def test_ook(self):
            with mock.patch(__name__ + '.ook', return_value=None):
                self.assertIsNone(ook())
            self.assertEquals(ook(), "the ook")
    
        # the patch decorator should work the same way, I just tend to use the
        # context manager out of personal preference
        @mock.patch(__name__ + '.ook', return_value=None)
        def test_ook_2(self, mock_ook):
            self.assertIsNone(ook())
    

    假设您已将该文件保存为 quicktest.py,则单元测试会给出以下结果:

    $ python -m unittest quicktest
    ..
    ----------------------------------------------------------------------
    Ran 2 tests in 0.001s
    
    OK
    

    当然,from a.b import c 在你的包中为你提供了一个普通变量c,所以同样的机制应该可以工作。

    【讨论】:

    • 你也可以使用__package__代替_name__来访问当前目录。
    • 这只是改变/打补丁测试模块中的功能,意味着src.py中原来导入的功能不会受到影响(打补丁)
    【解决方案3】:

    基于公认的答案,我相信这是实现预期目标的最干净的方法:

    from mock import patch
    from .. import monkey
    
    @patch(monkey.__name__+'.ook', Mock(return_value=None))
    def test_run_ook (self, mock_ook):
        self.assertIsNone(monkey.ook())
        mock_ook.run.assert_called_once_with('')
    

    【讨论】:

      【解决方案4】:

      不确定这是否是最好的方法(甚至推荐),但一种方法是使用类似的方法:

      from mock import patch,
      from ..monkey import ook
      [...]
      
      package_base = __package__.rsplit('.', 1)[0]
      
      @patch('{0}.monkey.ook'.format(package_base), Mock(return_value=None))
      def test_run_ook (self, mock_ook):
          self.assertIsNone(ook())
          mock_ook.run.assert_called_once_with('')
      

      【讨论】:

        【解决方案5】:

        当您从模块pkg1.pgk2.mymodule 执行from ..monkey import ook 时,您最终会得到pkg1.pgk2.mymodule.ook

        现在ook 位于执行from ... import ... 的模块的命名空间中。这就是您需要修补的目标。

        所以你只需修补pkg1.pkg2.mymodule.ook

        from unittest.mock import patch # mypackage.mymodule.patch
        from ..monkey import ook        # mypacket.mymodule.ook
        
        with patch("pkg1.pgk2.mymodule.ook"):
           ....
        

        正如其他人所指出的,如果您从执行导入的同一模块进行修补,那么您可以使用 __name__ 来获取点分包名称,但如果您是从另一个模块进行修补,则需要将其拼写出来.

        请记住,您导入的任何内容都可以从目标 modulethatimports.nameimported 修补。

        【讨论】:

          【解决方案6】:

          我认为这是因为您没有导入monkey,而是导入ook。如果您从 .. 导入猴子,那么它应该可以工作。否则,只需在ook 上调用补丁。

          【讨论】:

          • 当我这样做时,我会从 Mock 收到 TypeError: Need a valid target to patch. You supplied: ook
          • 然后执行 import monkey 而不是 import ook from 它,这样您的原始补丁才能真正起作用。
          • 它仍然无法工作,因为在 sys.path 的目录之一中似乎找不到 monkey。 (我猜这就是 Sardathrion 进行相对导入的原因。)事实上,补丁装饰器并不关心你的导入,因为它会自己导入有问题的模块。
          【解决方案7】:

          使用完整的导入路径。例如,如果你有这个文件系统:

          • 根/
            • 假人/
              • 富/
                • 模块.py
            • 假人2/
              • module2.py

          您可以使用以下方法从 module2.py 导入 module.py:

          from root.dummy.foo import module
          

          【讨论】:

          • -1 因为这不能回答我的问题。我想知道如何将它与相对路径和非绝对路径一起使用。
          • @Sardathrion 好的,但你在你的问题中没有说什么......你只是说你不想使用 sys.path
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2015-06-18
          • 1970-01-01
          • 2017-02-10
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多