【问题标题】:python: mock a modulepython:模拟一个模块
【发布时间】:2014-05-28 00:02:19
【问题描述】:

是否可以使用unittest.mock 在 python 中模拟模块?我有一个名为config 的模块,在运行测试时我想用另一个模块test_config 模拟它。我怎样才能做到这一点 ?谢谢。

config.py:

CONF_VAR1 = "VAR1"
CONF_VAR2 = "VAR2"

test_config.py:

CONF_VAR1 = "test_VAR1"
CONF_VAR2 = "test_VAR2" 

所有其他模块从config 模块读取配置变量。在运行测试时,我希望他们改为从 test_config 模块读取配置变量。

【问题讨论】:

    标签: python mocking


    【解决方案1】:

    如果您总是像这样访问 config.py 中的变量:

    import config
    ...
    config.VAR1
    

    您可以替换由您实际尝试测试的任何模块导入的config 模块。因此,如果您正在测试一个名为 foo 的模块,并且它导入并使用了 config,您可以说:

    from mock import patch
    import foo
    import config_test
    ....
    with patch('foo.config', new=config_test):
       foo.whatever()
    

    但这实际上并没有全局替换模块,它只是在 foo 模块的命名空间内替换它。所以你需要在它导入的任何地方修补它。如果foo 这样做而不是import config,它也不起作用:

    from config import VAR1
    

    你也可以用sys.modules来做这个:

    import config_test
    import sys
    sys.modules["config"] = config_test
    # import modules that uses "import config" here, and they'll actually get config_test
    

    但一般来说,惹sys.modules并不是一个好主意,我认为这种情况没有什么不同。我会赞成所有其他建议。

    【讨论】:

    • 非常好,虽然当我测试这个时,我无法在实际导入 foo 之前模拟foo.config(就像我可以用config.var1 说的那样)。这意味着导入 foo 时执行的任何代码都将使用原始配置模块。有什么建议吗?
    • @PeterGibson 对,这是这种方法的另一个限制。我不知道有什么办法。
    • @PeterGibson 我认为没有办法在导入 foo 时修补执行的代码而不会弄乱sys.modules。我已经在我的回答中描述了它。但除非这个导入补丁是必要的,否则我会坚持 dano 的方法。
    【解决方案2】:

    foo.py:

    import config
    
    VAR1 = config.CONF_VAR1
    
    def bar():
        return VAR1
    

    test.py:

    import unittest
    import unittest.mock as mock
    
    import test_config
    
    
    class Test(unittest.TestCase):
    
        def test_one(self):
            with mock.patch.dict('sys.modules', config=test_config):
                import foo
                self.assertEqual(foo.bar(), 'test_VAR1')
    

    如您所见,该补丁甚至适用于在 import foo 期间执行的代码。

    【讨论】:

    • 如果你的模块不是config而是config.foo怎么办?写mock.patch.dict('sys.modules', config.foo=test_config) 是不行的……
    • @naxa,您可以将字典传递给此函数patch.dict('sys.modules', {'config.foo': test_config}) (docs)
    • 如何为所有测试修补模块?
    • @kev,如果你的意思是一个TestCase中的所有测试,你应该在setUp()中创建一个带有patch.dict()start()的补丁程序,然后在tearDown()中创建一个stop()它。如果您指的是项目中的所有测试,那么这听起来像是一个很好的问题。我想你可以有一个带有单独配置的自定义测试运行器(django 似乎做了这样的事情)。但首先我会考虑改变设计以避免完全打补丁。
    • 如果模块已经导入到测试模块本身中,这似乎不起作用。例如,在我的单元测试中,我正在导入pyspark(第三方库)来为某些单元测试创​​建对象。但是在另一个模块中,我可以选择导入 pyspark。我想模拟从另一个模块导入的 pyspark 以引发 ImportError,但不能因为它已经在我的单元测试顶部导入。想法?
    【解决方案3】:

    如果你想模拟整个模块,只需模拟使用模块的导入。

    myfile.py

    import urllib
    

    test_myfile.py

    import mock
    import unittest
    
    class MyTest(unittest.TestCase):
    
      @mock.patch('myfile.urllib')
      def test_thing(self, urllib):
        urllib.whatever.return_value = 4
    

    【讨论】:

      【解决方案4】:

      考虑以下设置

      configuration.py:

      import os
      
      class Config(object):
          CONF_VAR1 = "VAR1"
          CONF_VAR2 = "VAR2"
      
      class TestConfig(object):
          CONF_VAR1 = "test_VAR1"
          CONF_VAR2 = "test_VAR2"
      
      
      if os.getenv("TEST"):
          config = TestConfig
      else:
          config = Config
      

      现在您可以在代码中的其他任何地方使用:

      from configuration import config
      print config.CONF_VAR1, config.CONF_VAR2
      

      当你想模拟你的配置文件时,只需设置环境变量“TEST”。

      额外学分: 如果您有很多配置变量在测试代码和非测试代码之间共享,那么您可以从 Config 派生 TestConfig 并简单地覆盖需要更改的变量:

      class Config(object):
          CONF_VAR1 = "VAR1"
          CONF_VAR2 = "VAR2"
          CONF_VAR3 = "VAR3"
      
      class TestConfig(Config):
          CONF_VAR2 = "test_VAR2"
          # CONF_VAR1, CONF_VAR3 remain unchanged
      

      【讨论】:

        【解决方案5】:

        如果你的应用程序(“app.py”说)看起来像

        import config
        print config.var1, config.var2
        

        并给出输出:

        $ python app.py
        VAR1 VAR2
        

        您可以使用mock.patch 来修补各个配置变量:

        from mock import patch
        
        with patch('config.var1', 'test_VAR1'):
            import app
        

        这会导致:

        $ python mockimport.py
        test_VAR1 VAR2
        

        虽然我不确定这在模块级别是否可行。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-05-04
          • 2010-09-22
          • 2020-12-09
          • 2013-04-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多