如何模拟库中的父类?

80 阅读2分钟

在 Python 中,我们经常需要模拟库中的父类来避免其副作用,同时又能测试自己的代码。例如,以下代码演示了如何模拟库类 HasSideEffects:

huake2_00020_.png

# 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 类的副作用被成功避免。