模拟对象的单元测试方法

2024-04-24 22:27:18 发布

您现在位置:Python中文网/ 问答频道 /正文

我试图弄到一套嘲弄对象的窍门,似乎被一些非常基本的东西弄糊涂了。我试图模拟对象MyClass,然后对它的一个方法进行单元测试。这是我的代码:

import mock
import unittest

class MyClass(object):
    def __init__(self, a):
        self.a = a
    def add_two(self):
        return self.a + 2

class TestMyClass(unittest.TestCase):
    @mock.patch('__main__.MyClass')
    def test_add_two(self, dummy_mock):
        m_my_class = mock.Mock()
        m_my_class.a = 10
        result = m_my_class.add_two() # I would expect the result to be 12
        import ipdb;ipdb.set_trace()
        self.assert_equal(result, 12)

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

m_my_class.a = 10中,我将a的值设置为to,然后在m_my_class.add_two()中添加一个2,难道我不应该得到12吗?然而,result是:

^{pr2}$

我错过了什么?在

由于我通过decorator将类的位置传递给测试方法@mock.patch('__main__.MyClass'),mocked不应该拥有所有的方法吗?因为如果不是,那么在decorator中包含什么类又有什么关系呢?在

编辑:

当我运行这个代码时,我仍然得到同样的结果。在

class TestMyClass(unittest.TestCase):
    @mock.patch('__main__.MyClass')
    def test_add_two(self, dummy_mock):
        dummy_mock.a = 10
        result = dummy_mock.add_two()
        import ipdb;ipdb.set_trace()
        self.assert_equal(result, 12)

结果:

ipdb> result
<MagicMock name='MyClass.add_two()' id='38647312'>

Tags: importselfaddmainmydefmyclassunittest
2条回答

修补类几乎肯定不是你想在这里做的。例如,如果您将测试方法更改为:

class TestMyClass(unittest.TestCase):
    @mock.patch('__main__.MyClass')
    def test_add_two(self, dummy_mock):
        m_my_class = MyClass(5)
        print m_my_class

你会发现这并不是你所期望的:

^{pr2}$

你会得到这个:

<MagicMock name='MyClass()' id='46477888'>

修补方法中的类意味着在方法中的任何时候,如果某个东西试图实例化类,它将得到一个模拟对象。在您的特定情况下,调用MyClass(5)将返回与调用dummy_mock相同的模拟对象。这允许您设置模拟对象,以便在您测试代码时,模拟对象将按您希望的方式运行。在

你真的只会把它用于依赖关系,而不是你正在测试的类。因此,例如,如果您的类从SQL中检索到数据,那么您应该修补SQL类以使您的类具有mock而不是普通的SQL连接。这使您能够独立地测试一个类,而不必担心外部(如SQL)会妨碍您。在

不过,在您的示例中,您似乎在尝试单独测试一个方法。一种方法是从方法中提取函数并使用它。代码:

def test_add_two(self):
    test_mock = mock.Mock()
    test_mock.add_two = MyClass.add_two.__func__
    test_mock.a = 10
    result = test_mock.add_two(test_mock)
    self.assert_equal(result, 12)

通常,您不必为self参数传入参数,但正如这里我们已经拉出了您确实需要传入self参数的函数。如果需要,可以将函数转换为绑定到模拟对象的方法,如下所示:

import types
...
    test_mock.add_two = types.MethodType(MyClass.add_two.__func__, test_mock, test_mock.__class__)
    result = test_mock.add_two()

如果正在测试的函数调用任何其他方法,则需要对每个方法执行此操作或模拟它。在

我建议不要过多地使用这种策略,部分原因是需要处理被测试方法调用的方法。当然,能够模仿它所依赖的方法可能正是你想要做的。无论如何,它要求单元测试对方法如何工作有相当深入的理解,而不仅仅是它应该产生什么。这使得测试变得非常脆弱,使得您很可能比正在测试的方法更经常地看到测试中断。在

你为什么嘲笑你的小苏?这通常是你应该避免做的事情,因为它会带来风险,你不会去测试你认为你正在测试的东西。在

单元测试的目的是在完全隔离的情况下验证单元的行为。一个单元通常与其他单元协作以提供一些有用的功能。Test doubles(mock、fakes等)是用来实现这种隔离的工具。在单元测试中,SUT的协作者被替换为测试双精度,以便minimize the number of moving parts。在

您的SUT MyClass似乎没有任何合作者。因此,不需要双重测试来单独测试这个单元(它已经是独立的)。这样可以大大简化单元测试:

import mock
import unittest

class MyClass(object):
    def __init__(self, a):
        self.a = a
    def add_two(self):
        return self.a + 2

class TestMyClass(unittest.TestCase):
    def test_add_two(self):
        m_my_class = MyClass()
        m_my_class.a = 10
        result = m_my_class.add_two() # I would expect the result to be 12
        import ipdb;ipdb.set_trace()
        self.assert_equal(result, 12)

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

编辑:下面的代码演示如何使用模拟对象。(免责声明:我通常不使用Python,所以我的代码可能是not very idiomatic。不过,希望核心点还是有道理的。)

在本例中,MyClass添加由协作者提供的值,而不是核心值(2)。在

^{pr2}$

上面的例子使用了一个模拟对象,而不是修补一个类。这里有一个很好的解释:

Mocking a class: Mock() or patch()?

相关问题 更多 >