我试图搞砸一些嘲弄对象,似乎被一些非常基本的东西搞糊涂了。我试图模拟对象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'>
答案 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()
上面的示例使用模拟对象,而不是修补类。以下是对差异的一个非常好的解释: