【问题标题】:Python - Mocking chained function callsPython - 模拟链式函数调用
【发布时间】:2015-12-16 09:40:48
【问题描述】:

我在单元测试的方法之一中有如下语句。

db_employees = self.db._session.query(Employee).filter(Employee.dept ==   
    new_employee.dept).all()

我希望 db_employees 获得员工的模拟列表。我尝试使用以下方法实现此目的:

 m = MagickMock()
 m.return_value.filter().all().return_value = employees

其中雇员是雇员对象的列表。但这没有用。当我尝试打印任何属性的值时,它都有一个模拟值。代码是这样的:

class Database(object):
    def __init__(self, user=None, passwd=None, db="sqlite:////tmp/emp.db"):
        try:
            engine = create_engine(db)
        except Exception:
            raise ValueError("Database '%s' does not exist." % db)

        def on_connect(conn, record):
            conn.execute('pragma foreign_keys=ON')

        if 'sqlite://' in db:
            event.listen(engine, 'connect', on_connect)
        Base.metadata.bind = engine
        DBSession = sessionmaker(bind=engine)
        self._session = DBSession()


class TestEmployee(MyEmployee):
    def setUp(self):
        self.db = emp.database.Database(db=options.connection)
        self.db._session._autoflush()

    @mock.patch.object(session.Session, 'add')     
    @mock.patch.object(session.Session, 'query')  
    def test_update(self, mock_query, mock_add):
        employees = [{'id': 1,
                      'name': 'Pradeep',
                      'department': 'IT',
                      'manager': 'John'}]
        mock_add.side_effect = self.add_side_effect
        mock_query.return_value = self.query_results()  
        self.update_employees(employees)

    def add_side_effect(self, instance, _warn=True):
        // Code to mock add
        // Values will be stored in a dict which will be used to 
        // check with expected value.

    def query_results(self):  
        m = MagicMock()  
        if self.count == 0:  
             m.return_value.filter.return_value.all.return_value = [employee]  
        elif:  
             m.return_value.filter.return_value.all.return_value = [department]  
        return m  

我有 query_results 作为被测方法调用 query 两次。首先是员工表,然后是部门表。

如何模拟这个链式函数调用?

【问题讨论】:

  • 首先将m.return_value.filter... 替换为m.filter...,因为m 已经设置为query 返回值。并在您的问题中添加mock_query.mock_calls 为空。据我了解,您正在修补错误的对象。
  • 现在尝试使用@mock.patch(__name__ + '.DBSession.query') 修补查询。

标签: python mocking chained


【解决方案1】:
m = MagickMock()
m.session.query().filter().all.return_value = employees

https://docs.python.org/3/library/unittest.mock.html

【讨论】:

  • 这里不需要使用MagicMock,因为没有调用特殊方法。
  • 啊,这太棒了——我们一直在手动创建链接方法的结果,例如。 obj = Mock(); Object = Mock(); Object.return_value = obj 但是一旦有超过 1 级的链接,它就会停止工作,而 package.one().two().three().return_value = ... 就是所有工作
  • 在执行m.session.query.assert_called_once() 之类的操作时,您如何绕过Expected 'filter' to have been called once. Called 2 times.
  • 这使得你的测试依赖于实现细节并且相当脆弱……真的没有更好的方法吗?
【解决方案2】:

我找到了一个类似问题的解决方案,我需要模拟一组嵌套的过滤调用。

给定的测试代码类似于以下内容:

interesting_cats = (session.query(Cats)
                           .filter(Cat.fur_type == 'furry')
                           .filter(Cat.voice == 'meowrific')
                           .filter(Cat.color == 'orande')
                           .all())

你可以像下面这样设置模拟:

mock_session_response = MagicMock()
# This is the magic - create a mock loop
mock_session_response.filter.return_value = mock_session_response
# We can exit the loop with a call to 'all'
mock_session_response.all.return_value = provided_cats

mock_session = MagicMock(spec=Session)
mock_session.query.return_value = mock_session_response

【讨论】:

    【解决方案3】:

    您应该修补_sessionDatabase 属性的query() 方法并对其进行配置以提供正确的答案。你可以通过很多方式做到这一点,但恕我直言,更简洁的方法是修补 DBSessionquery 静态引用。我不知道你从 wwitch 模块中导入了DBSession,所以我会修补本地参考。

    另一方面是模拟配置:我们将设置query 的返回值,在您的情况下成为具有filter() 方法的对象。

    class TestEmployee(MyEmployee):
        def setUp(self):
            self.db = emp.database.Database(db=options.connection)
            self.db._session._autoflush()
            self.log_add = {}
    
        @mock.patch.object(__name__.'DBSession.add')     
        @mock.patch.object(__name__.'DBSession.query')  
        def test_update(self, mock_query, mock_add):
            employees = [{'id': 1,
                          'name': 'Pradeep',
                          'department': 'IT',
                          'manager': 'John'}]
            mock_add.side_effect = self.add_side_effect
            mock_query.return_value = self.query_results()  
            self.update_employees(employees)
            .... your test here
    
        def add_side_effect(self, instance, _warn=True):
            # ... storing data
            self.log_add[...] = [...]
    
        def query_results(self):  
            m = MagicMock()
            value = "[department]"
            if not self.count:  
                 value = "[employee]"  
            m.filter.return_value.all.return_value = value 
            return m
    

    【讨论】:

    • 成功了。将 mock_query.return_value 更改为 mock_query.side_effect = self.query_results。现在我能够按预期获得对象。我接受你的回答,因为它帮助我解决了链式函数调用。
    猜你喜欢
    • 2011-04-18
    • 2011-07-16
    • 2019-10-31
    • 2021-12-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-07-08
    相关资源
    最近更新 更多