单元测试模拟对象的方法

时间:2014-04-23 01:54:46

标签: python unit-testing mocking

我试图搞砸一些嘲弄对象,似乎被一些非常基本的东西搞糊涂了。我试图模拟对象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的值设置为,然后在m_my_class.add_two()中添加两个,我不应该得到12吗?但是,result是:

     16         import ipdb;ipdb.set_trace()
---> 17         self.assert_equal(result, 12)
     18 

ipdb> result
<Mock name='mock.add_two()' id='18379792'>

我错过了什么?

由于我通过装饰器将类的位置传递给测试方法@mock.patch('__main__.MyClass'),所以不应该嘲笑所有的方法吗?因为如果没有,那么为什么我们在装饰器中包含哪个类呢?

编辑:

当我运行此代码时,我仍然得到同样的东西。

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'>

2 个答案:

答案 0 :(得分: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

你会发现,而不是得到你期望的:

<__main__.MyClass object at 0x0000000002C53E48>

你会得到这个:

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

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

你真的只会将它用于依赖项,而不是你正在测试的类。因此,例如,如果您的类从SQL检索数据,那么您将修补SQL类以使您的类成为模拟而不是正常的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()

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

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

答案 1 :(得分:2)

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

单元测试的目的是验证单元完全隔离的行为。一个单元通常与其他单元协作以提供一些有用的功能。 Test doubles(模拟,假货等)是用于实现这种隔离的工具。在单元测试中,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)。

import mock
import unittest

class MyClass(object):
    def __init__(self, a, get_value):
        self.a = a
        self.get_value = get_value
    def add_value(self):
        return self.a + self.get_value()

class TestMyClass(unittest.TestCase):
    def test_add_value(self):
        m_test_value = 42
        m_test_a = 10
        m_my_class = MyClass()
        m_get_test_value = mock.Mock(return_value=m_test_value)
        m_my_class.a = test_a

        result = m_my_class.add_value()
        import ipdb;ipdb.set_trace()
        self.assert_equal(result, m_test_a + m_test_value)

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

上面的示例使用模拟对象,而不是修补类。以下是对差异的一个非常好的解释:

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