在 Python 中,我们经常需要模拟库中的父类来避免其副作用,同时又能测试自己的代码。例如,以下代码演示了如何模拟库类 HasSideEffects:
# library.py
class HasSideEffects(object):
def __init__(self, kittens):
print('kittens cry') # <- side effect
def launch_nukes(self):
print('launching nukes') # <- side effect
# my_code.py
from library import HasSideEffects
class UtilitySubclass(HasSideEffects):
def party(self):
self.launch_nukes()
return 'confetti' # I want to test that my confetti cannon will work
if __name__ == '__main__':
x = UtilitySubclass(1) # oh no! crying kittens :(
x.party() # oh no! nukes!
当运行 my_code.py 时,将会看到以下输出:
kittens cry
launching nukes
这表明 HasSideEffects 类中的 init 和 launch_nukes 方法产生了副作用。现在,我们希望编写单元测试来测试 UtilitySubclass 类的 party 方法,但我们不想在测试中产生这些副作用。
2、解决方案
我们可以使用 unittest.mock 库来模拟 HasSideEffects 类,并避免其副作用。具体做法如下:
import unittest
from unittest.mock import Mock, patch
# library.py
class HasSideEffects(object):
def __init__(self, kittens):
print('kittens cry') # <- side effect
def launch_nukes(self):
print('launching nukes') # <- side effect
# my_code.py
from library import HasSideEffects
class UtilitySubclass(HasSideEffects):
def party(self):
self.launch_nukes()
return 'confetti' # I want to test that my confetti cannon will work
class TestUtilitySubclass(unittest.TestCase):
@patch('library.HasSideEffects')
def test_party(self, mock_HasSideEffects):
# Mock the __init__ and launch_nukes methods to avoid side effects
mock_HasSideEffects.__init__ = Mock(return_value=None)
mock_HasSideEffects.launch_nukes = Mock(return_value=None)
# Create an instance of UtilitySubclass and call the party method
subclass = UtilitySubclass(1)
result = subclass.party()
# Assert that the expected confetti was returned
self.assertEqual(result, 'confetti')
# Assert that the methods were called as expected
mock_HasSideEffects.__init__.assert_called_once_with(1)
mock_HasSideEffects.launch_nukes.assert_called_once_with()
if __name__ == '__main__':
unittest.main()
在测试方法 test_party 中,我们使用了 patch 装饰器来模拟 HasSideEffects 类。我们通过 mock_HasSideEffects.init = Mock(return_value=None) 和 mock_HasSideEffects.launch_nukes = Mock(return_value=None) 来模拟 init 和 launch_nukes 方法,并分别让它们返回 None。
然后,我们创建 UtilitySubclass 的一个实例,并调用 party 方法。最后,我们使用 assertEqual 来断言返回的结果为 'confetti',并使用 assert_called_once_with 来断言 init 和 launch_nukes 方法被调用了预期次数。
运行测试代码,将会看到以下输出:
.
----------------------------------------------------------------------
Ran 1 test in 0.000s
OK
这表明测试通过,且 HasSideEffects 类的副作用被成功避免。