Python - 模拟链式函数调用

34

我在一个单元测试方法中有以下语句。

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

employees是一个员工对象列表。但是这样做并没有起作用。当我尝试打印任何属性的值时,它都有一个模拟值。代码如下:

其中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会调用两次查询,先是employee表,然后是department表。

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


首先,将 m.return_value.filter... 替换为 m.filter...,因为 m 已经被设置为 query 的返回值。然后在你的问题中添加 mock_query.mock_calls 为空的信息。据我所知,你正在修补错误的对象。 - Michele d'Amico
现在尝试使用@mock.patch(__name__ + '.DBSession.query')来修补查询。 - Michele d'Amico
3个回答

43

1
啊,这太棒了——我们一直在手动创建链接方法的结果,例如 obj = Mock(); Object = Mock(); Object.return_value = obj,但是一旦有超过1个级别的链接,这种方法就不再起作用了,而 package.one().two().three().return_value = ... 就是唯一有效的方法。 - kevlarr
1
当执行类似于 m.session.query.assert_called_once() 的操作时,如何避免出现 Expected 'filter' to have been called once. Called 2 times. 的错误提示? - themanatuf
这会使你的测试依赖于实现细节,而且非常脆弱...难道真的没有更好的方法吗? - ronathan
这将增加模拟器的调用栈。你可以通过使用return_values来避免这个问题:m.session.query.return_value.filter.return_value.all.return_value = employees - Spencer

8

我发现了一个解决方案,这个方案可以模拟嵌套的滤波调用。

给定一个类似以下代码的测试代码:

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

0

你应该修补_sessionDatabase属性的query()方法,并将其配置为给出正确的答案。你可以用很多种方式来做到这一点,但在我看来,最干净的方式是修补DBSessionquery静态引用。我不知道你从哪个模块导入了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。现在我能够按预期获取对象了。我接受你的答案,因为它帮助我解决了链式函数调用的问题。 - Pradeep

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