模拟整个Python类

32

我想在Python中进行一个简单的测试,但我不知道如何完成mocking过程。

这是类和def代码:

class FileRemoveOp(...)
    @apply_defaults
    def __init__(
            self,
            source_conn_keys,
            source_conn_id='conn_default',
            *args, **kwargs):
        super(v4FileRemoveOperator, self).__init__(*args, **kwargs)
        self.source_conn_keys = source_conn_keys
        self.source_conn_id = source_conn_id


    def execute (self, context)
          source_conn = Connection(conn_id)
          try:
              for source_conn_key in self.source_keys:
                  if not source_conn.check_for_key(source_conn_key):    
                      logging.info("The source key does not exist")  
                  source_conn.remove_file(source_conn_key,'')
          finally:
              logging.info("Remove operation successful.")

这是我对「execute」函数的测试:

@mock.patch('main.Connection')
def test_remove_execute(self,MockConn):
    mock_coon = MockConn.return_value
    mock_coon.value = #I'm not sure what to put here#
    remove_operator = FileRemoveOp(...)
    remove_operator.execute(self)

由于execute方法尝试建立连接,我需要模拟它,我不想建立真正的连接,只需返回一些模拟数据。我如何做到这一点?我习惯在Java中进行测试,但我从未在Python中进行过。


1
你实际上想要测试什么?通常情况下,你会模拟被测试代码调用的东西,而不是直接调用模拟。采用你的方法,你只会测试到模拟本身。 - Klaus D.
一个更完整的例子会更有用。很难判断你想要实现什么。话虽如此,当调用模拟类时,它将自动创建另一个模拟作为返回的实例。然后,您可以根据需要设置这个模拟连接。 - Dunes
2个回答

54

首先需要明确的是,根据 unittest.mock 文档,你总是需要模拟出试图模拟的东西。

基本原则是,在对象被查找的地方打补丁(patch),这个地方不一定和定义它的地方相同。

接下来你需要做的是将一个 MagicMock 实例作为修补(patch)后的对象的 return_value 返回。总结一下,你需要按照以下顺序进行操作:

  • 修补(patch)对象
  • 准备要使用的 MagicMock
  • 返回刚刚创建的 MagicMock 作为 return_value

这里有一个快速示例项目。

connection.py (我们想要模拟的类)

class Connection(object):                                                        
    def execute(self):                                                           
        return "Connection to server made"

file.py(类被使用的地方)

from project.connection import Connection                                        


class FileRemoveOp(object):                                                      
    def __init__(self, foo):                                                     
        self.foo = foo                                                           

    def execute(self):                                                           
        conn = Connection()                                                      
        result = conn.execute()                                                  
        return result    

测试/tests/test_file.py

import unittest                                                                  
from unittest.mock import patch, MagicMock                                       
from project.file import FileRemoveOp                                            

class TestFileRemoveOp(unittest.TestCase):                                       
    def setUp(self):                                                             
        self.fileremoveop = FileRemoveOp('foobar')                               

    @patch('project.file.Connection')                                            
    def test_execute(self, connection_mock):
        # Create a new MagickMock instance which will be the
        # `return_value` of our patched object                                     
        connection_instance = MagicMock()                                        
        connection_instance.execute.return_value = "testing"

        # Return the above created `connection_instance`                     
        connection_mock.return_value = connection_instance                       

        result = self.fileremoveop.execute()                                     
        expected = "testing"                                                     
        self.assertEqual(result, expected)                                       

    def test_not_mocked(self):
        # No mocking involved will execute the `Connection.execute` method                                                   
        result = self.fileremoveop.execute()                                     
        expected = "Connection to server made"                                   
        self.assertEqual(result, expected) 

2
非常感谢!这是一个完美的例子,而且运行正确! :) - AnaF
连接 Mock 是什么?它是最后一个参数吗?如果您有两个要模拟的事情,怎么办? - Jwan622
如果您想向该方法添加第二个补丁,则必须使用两个patch装饰器,这些装饰器可以在方法签名中以倒序访问。这里是一个示例 - flazzarini
4
这个例子很棒。 - Kyle Bridenstine
在这种情况下,使用MagicMock替换库的函数,并将创建的MagicMockreturn_value设置为您模拟的对象。 - flazzarini
显示剩余5条评论

0
我发现在Python3中这个简单的解决方案可行:在第一次导入之前,你可以替换整个类。比如说,我需要模拟真实的.manager中的类“Manager”。
class MockManager:
    ...

import real.manager
real.manager.Manager = MockManager

如果没有更好的地方,可以在init.py中进行这种替换。

它可能也适用于Python2,但我没有检查过。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接