在 python 单元测试中模拟 class 和 class 方法

Mock a class and a class method in python unit tests

我正在使用 python 的 unittest.mock 在 Django 应用程序中进行一些测试。我想检查 class 是否被调用,并且其实例上的方法也被调用。

例如,给出这个简化的示例代码:

# In project/app.py
def do_something():
    obj = MyClass(name='bob')
    return obj.my_method(num=10)

这个测试来检查发生了什么:

# In tests/test_stuff.py
@patch('project.app.MyClass')
def test_it(self, my_class):
    do_something()
    my_class.assert_called_once_with(name='bob')
    my_class.my_method.assert_called_once_with(num=10)

测试成功表明 my_class 被调用,但 my_class.my_method 未被调用。我知道我遗漏了一些东西——在被模拟的 class 上模拟一个方法? - 但我不确定是什么或如何让它发挥作用。

您的第二个模拟断言需要测试您是在 实例 上调用 my_method,而不是 class 本身。

像这样调用模拟对象,

my_class().my_method.assert_called_once_with(num=10)
        ^^

这一行

 my_class.my_method.assert_called_once_with(num=10)
如果 my_method 是 class 方法,

将起作用。

是这样吗?

否则,如果my_method只是一个普通的实例方法,那么你将需要重构函数do_something来获取实例变量obj

例如

def do_something():
    obj = MyClass(name='bob')
    return obj, obj.my_method(num=10)

# In tests/test_stuff.py
@patch('project.app.MyClass')
def test_it(self, my_class):
    obj, _ = do_something()
    my_class.assert_called_once_with(name='bob')
    obj.my_method.assert_called_once_with(num=10)

对您的单元测试的一个小的重构建议,以帮助您处理您在测试中可能遇到的其他实例方法。您可以在 setUp 方法中设置所有这些,而不是在每个方法中模拟您的 class 。这样,通过模拟 class 并从 class 创建模拟对象,您现在可以根据需要多次使用该对象,测试 class 中的所有方法.

为了帮助说明这一点,我整理了以下示例。在线评论:

class MyTest(unittest.TestCase):

    def setUp(self):
        # patch the class
        self.patcher = patch('your_module.MyClass')
        self.my_class = self.patcher.start()

        # create your mock object
        self.mock_stuff_obj = Mock()
        # When your real class is called, return value will be the mock_obj
        self.my_class.return_value = self.mock_stuff_obj

    def test_it(self):
        do_something()

        # assert your stuff here
        self.my_class.assert_called_once_with(name='bob')
        self.mock_stuff_obj.my_method.assert_called_once_with(num=10)

    # stop the patcher in the tearDown
    def tearDown(self):
        self.patcher.stop()

为了深入了解这些是如何组合在一起的,在 setUp 方法中,我们将提供跨多个方法应用补丁的功能,如文档 here.[=18= 中所述]

补丁是在这两行中完成的:

    # patch the class
    self.patcher = patch('your_module.MyClass')
    self.my_class = self.patcher.start()

最后,模拟对象在这里创建:

    # create your mock object
    self.mock_stuff_obj = Mock()
    self.my_class.return_value = self.mock_stuff_obj

现在,您所有的测试方法都可以在所有调用中简单地使用 self.my_classself.mock_stuff_obj