如何模拟一个调用后改变实例变量的Python类?

5 投票
1 回答
7585 浏览
提问于 2025-04-21 09:34

我有一些代码,其中调用了一个叫做 update() 的方法,这个方法会改变一些实例变量的值。我用这个改变后的值来退出一个循环。下面是我代码的简化示例:

def do_stuff(self):
    # get a new instance of A
    a = get_a()
    while True:
        a.update()
        if a.state == 'state':
            break

这是这个类的简单版本(我不能修改这个类,因为它是第三方库的):

class A(object):
    def __init__(self):
        self.state = ''

    def update(self):
        # call to external system
        self.state = extern_func()

现在我想通过模拟类 A 来测试我的函数 do_stuff()。为了测试这个函数的各个方面,我想要获取 state 的所有不同值,并且它应该在每次调用 a.update() 后发生变化(也就是遍历不同的状态)。

我为我的单元测试设置了这样的环境:

from mock import Mock, patch
import unittest

class TestClass(unittest.TestClass):

    @patch('get_a')
    def test_do_stuff(self, mock_get_a):
         mock_a = Mock(spec=A)
         mock_get_a.return_value = mock_a

         # do some assertions

我可以通过 Mock 实现这样的行为吗?我知道 Mockside_effect 可以在连续的函数调用中返回不同的值。但我找不到在函数调用后改变实例变量值的方法?

1 个回答

1

设置:

from mock import Mock, MagicMock, patch

sep = '***********************\n'

# non-mock mocks
def get_a():
    return A()
def extern_func():
    return 'foo'

def do_stuff(self):
    # get a new instance of A
    a = get_a()
    while True:
        a.update()
        print a.state
        if a.state == 'state':
            break

class A(object):
    def __init__(self):
        self.state = ''

    def update(self):
        # call to external system
        self.state = extern_func()

正如 @Simeon Viser 提到的,模拟 extern_func 是可行的:

print sep, 'patch extern'
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
@patch('__main__.extern_func', mock)
def foo():
    do_stuff(3)
foo()

>>> 
***********************
patch extern
1
2
3
4
state

side_effect 可以是一个函数,你可以通过使用 auto_spec = True 参数来模拟未绑定的方法 A.update

使用上下文管理器:

print sep, 'patch update context manager call do_stuff'
def update_mock(self):
    self.state = mock()
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
with patch.object(A, 'update', autospec = True) as mock_update:
    mock_update.side_effect = update_mock
    do_stuff(3)

>>>
***********************
patch update context manager call do_stuff
1
2
3
4
state

使用装饰器:

print sep, 'patch update decorator test_do_stuff'
def update_mock(self):
    self.state = mock()
mock = MagicMock(side_effect = [1,2,3,4, 'state'])
@patch.object(A, 'update', autospec = True, side_effect = update_mock)
def test_do_stuff(self):
    do_stuff(3)
test_do_stuff()

>>>
***********************
patch update decorator test_do_stuff
1
2
3
4
state

注意:我从来没有写过全面的单元测试,最近才开始阅读模拟的文档,所以虽然我似乎让这个工作了,但我不能评论它在你的测试方案中的有效性。欢迎修改。

撰写回答