Python 模拟对象的单元测试方法
我试图掌握模仿对象的窍门,似乎被一些非常基本的东西弄糊涂了。我试图模拟对象MyClass,然后对其中一个方法进行单元测试。这是我的密码:Python 模拟对象的单元测试方法,python,unit-testing,mocking,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.TestCa
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()
中我添加了一个2,我不应该得到12吗?但是,结果是:
16 import ipdb;ipdb.set_trace()
---> 17 self.assert_equal(result, 12)
18
ipdb> result
<Mock name='mock.add_two()' id='18379792'>
结果:
ipdb> result
<MagicMock name='MyClass.add_two()' id='38647312'>
ipdb>结果
在这里,修补类几乎肯定不是您想要做的事情。例如,如果您将测试方法更改为:
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>
通常情况下,您不必为self
参数传入参数,但在这里,我们提取了您确实需要传入self
参数的函数。如果需要,可以将函数转换为绑定到模拟对象的方法,如下所示:
import types
...
test_mock.add_two = types.MethodType(MyClass.add_two.__func__, test_mock, test_mock.__class__)
result = test_mock.add_two()
如果您正在测试的函数调用任何其他方法,那么您需要为每个方法执行此操作或模拟它
我建议不要过多地使用这种策略,部分原因是需要注意被测试方法调用的方法。当然,能够模仿它所依赖的方法可能正是你想要做的。无论如何,它要求单元测试对方法的工作原理有相当深刻的理解,而不仅仅是它应该产生什么。这使得测试非常脆弱,因此您可能会比正在测试的方法更频繁地看到测试中断。修补类几乎肯定不是您希望在这里做的事情。例如,如果您将测试方法更改为:
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>
通常情况下,您不必为self
参数传入参数,但在这里,我们提取了您确实需要传入self
参数的函数。如果需要,可以将函数转换为绑定到模拟对象的方法,如下所示:
import types
...
test_mock.add_two = types.MethodType(MyClass.add_two.__func__, test_mock, test_mock.__class__)
result = test_mock.add_two()
如果您正在测试的函数调用任何其他方法,那么您需要为每个方法执行此操作或模拟它
我建议不要过多地使用这种策略,部分原因是需要注意被测试方法调用的方法。当然,能够模仿它所依赖的方法可能正是你想要做的。无论如何,它要求单元测试对方法的工作原理有相当深刻的理解,而不仅仅是它应该产生什么。这使得测试非常脆弱,因此您可能会比您正在测试的方法更频繁地看到测试中断。为什么您要嘲笑您的SUT?这通常是你应该避免做的事情,因为这会带来你不会测试你认为你正在测试的东西的风险
单元测试的目的是验证完全隔离的单元的行为。一个单元通常与其他单元协作以提供一些有用的功能。(模拟、伪造等)是用于实现这种隔离的工具。在单元测试中,SUT的合作者被替换为测试替身,以便进行测试
您的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中工作,所以我的代码可能是。不过,希望核心点仍然有意义。)
在本例中,MyClass
添加了由合作者提供的值,而不是核心值(2)
上面的示例使用模拟对象,而不是修补类。以下是对差异的一个很好的解释:
你为什么要嘲笑你的SUT?这通常是你应该避免做的事情,因为这会带来你不会测试你认为你正在测试的东西的风险
单元测试的目的是验证完全隔离的单元的行为。一个单元通常与其他单元协作以提供一些有用的功能。(模拟、伪造等)是用于实现这种隔离的工具。在单元测试中,SUT的合作者被替换为测试替身,以便进行测试
您的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中工作,所以我的代码可能是。不过,希望核心点仍然有意义。)
在本例中,MyClass
添加了由合作者提供的值,而不是核心值(2)
上面的示例使用模拟对象,而不是修补类。以下是对差异的一个很好的解释:
我认为我不理解第一部分。如果不是这样:result=m_my_class.add_two()
我运行result=m_my_class.没有这样的方法()
,我会得到相同的精确结果,因此似乎add_two
是MyClass
的方法这一事实在这里并不相关。@Akavall很抱歉,我在你的代码中遗漏了一个导入细节。我已经更新了我的答案。谢谢,但即使我使用dummy\u mock
,我还是得到了同样的答案。我更新了问题,以显示我正在使用的代码。@Akavall我比我想象中的mock更粗糙了一点。我提醒自己到底发生了什么,并再次更新了我的答案。@Akavall我发现了如何单独测试一个方法,这正是您在示例中试图做的。我已将其添加到我的answ中