用Python模拟一个类方法并更改一些对象属性。

15

我在Python中刚开始使用模拟(mock)功能。我想知道如何在测试时用另一个方法替换(mock)类方法,同时了解原始方法只是更改self的一些属性而不返回任何值。例如:

def some_method(self):   
    self.x = 4   
    self.y = 6   

所以在这里我无法改变模拟结果的返回值。我尝试定义一个新函数(应该替换原来的函数),并将其作为side_effect给予模拟对象。但是如何使模拟函数更改类中对象的属性呢?以下是我的代码:

@patch('path.myClass.some_method')
def test_this(self,someMethod):

    def replacer(self):
        self.x = 5
        self.y = 16

some_method.side_effect = replacer
那么Python现在如何理解替换器的self参数呢?它是测试类的self,还是被测试类对象自身的self?

那么Python现在如何理解替换器的self参数呢?它是测试类的self,还是被测试类对象自身的self?

1个回答

29

提前道歉,如果我没有理解您尝试做什么,但我认为这可能有效:

import unittest
from unittest.mock import patch

class MyClass:

    def __init__(self):
        self.x = 0
        self.y = 0

    def some_method(self):   
        self.x = 4   
        self.y = 6    

class OtherClass:

    def other_method(self):
        self.x = 5
        self.y = 16

class MyTestClass(unittest.TestCase):

    @patch('__main__.MyClass.some_method', new=OtherClass.other_method)
    def test_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 5)
        self.assertEqual(a.y, 16)

    def test_not_patched(self):
        a = MyClass()
        a.some_method()
        self.assertEqual(a.x, 4)
        self.assertEqual(a.y, 6)

if __name__ == "__main__":
    unittest.main()

当打补丁时,此操作将 some_method() 替换为 other_method(),并为属性 x、y 设置不同的值。运行测试时,会得到以下结果:

..
----------------------------------------------------------------------
Ran 2 tests in 0.020s

OK

编辑:回答如何在测试函数中不使用模拟类的问题...

def test_inside_patch(self):
    def othermethod(self):
        self.x = 5
        self.y = 16
    patcher = patch('__main__.MyClass.some_method', new=othermethod)
    patcher.start()
    a = MyClass()
    a.some_method()
    self.assertEqual(a.x, 5)
    self.assertEqual(a.y, 16) 
    patcher.stop()
确保在patcher上调用start()和stop(),否则可能会出现补丁处于活动状态但您不希望它处于活动状态的情况。请注意,在测试代码函数内定义模拟函数时,我没有使用patch作为装饰器,因为必须在patch中使用“new”关键字之前定义模拟函数。如果您想使用patch作为装饰器,您必须在patch之前定义模拟函数,将其定义在MyTestClass中也可以工作,但似乎您真的希望在测试函数代码内定义模拟函数。
编辑:添加了我看到的四种方法的摘要...
# first way uses a class outside MyTest class
class OtherClass:
    def other_method(self):
        ...

class MyTest(unittest.TestCase):

    @patch('path_to_MyClass.some_method', new=OtherClass.other_method)
    def test_1(self)
        ...

    # 2nd way uses class defined inside test class    
    class MyOtherClass:
        def other_method(self):
            ...
    @patch('path_to_MyClass.some_method', new=MyOtherClass.other_method)    
    def test_2(self):
        ...

    # 3rd way uses function defined inside test class but before patch decorator 
    def another_method(self):
        ...
    @patch('path_to_MyClass.some_method', new=another_method)    
    def test_3(self):
        ...

    # 4th way uses function defined inside test function but without a decorator
    def test_4(self):
        def yet_another_method(self):
            ...
        patcher = patch('path_to_MyClass.some_method', new=yet_another_method)
        patcher.start()
        ...
        patcher.stop()

这些都没有使用side_effect,但它们都解决了模拟类方法和更改一些属性的问题。你选择哪个取决于应用程序。


谢谢你的回答。非常有帮助。但是创建一个新类,然后在这个新类中创建一个新方法是必要的吗?我不能只是在测试代码中定义other_method,然后将其作为side_effect吗? - Ahmed Ayadi
1
@AhmedAyadi 是的,可以在测试函数代码中定义模拟函数,但我不确定是否可以使用side_effect。请参见我对帖子的编辑。附言:如果此答案解决了您的问题,请接受它,谢谢! - Steve Misuta
非常感谢 :) 这确实回答了我的问题。 - Ahmed Ayadi
@SteveMisuta 感谢您的出色回答。我有一个后续问题。在我的用例中,我需要在循环中使用模拟方法(“other_method()”),并希望在每次迭代中为类属性(x和y)分配不同的值。我尝试将自己的模拟函数列表分配给“new”参数,但是“new”不像side_effect一样接受可迭代对象。我想知道是否有任何解决此类用例的方法? - Saurabh Agrawal

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