如何在单元测试中正确使用python模拟setUp
在我尝试学习TDD时,试图学习单元测试和使用python进行模拟。慢慢地掌握它,但不确定我是否正确地做到了这一点。预先警告:我使用python 2.4,因为供应商API是预编译的2.4 pyc文件,所以我使用模拟0.8.0和单元测试(不是unittest2)如何在单元测试中正确使用python模拟setUp
鉴于此示例代码在'mymodule.py '
import ldap
class MyCustomException(Exception):
pass
class MyClass:
def __init__(self, server, user, passwd):
self.ldap = ldap.initialize(server)
self.user = user
self.passwd = passwd
def connect(self):
try:
self.ldap.simple_bind_s(self.user, self.passwd)
except ldap.INVALID_CREDENTIALS:
# do some stuff
raise MyCustomException
现在在我的测试用例文件'test_myclass.py'中,我想模拟ldap对象。 ldap.initialize返回ldap.ldapobject.SimpleLDAPObject,所以我想这将是我不得不嘲笑的方法。
import unittest
from ldap import INVALID_CREDENTIALS
from mock import patch, MagicMock
from mymodule import MyClass
class LDAPConnTests(unittest.TestCase):
@patch('ldap.initialize')
def setUp(self, mock_obj):
self.ldapserver = MyClass('myserver','myuser','mypass')
self.mocked_inst = mock_obj.return_value
def testRaisesMyCustomException(self):
self.mocked_inst.simple_bind_s = MagicMock()
# set our side effect to the ldap exception to raise
self.mocked_inst.simple_bind_s.side_effect = INVALID_CREDENTIALS
self.assertRaises(mymodule.MyCustomException, self.ldapserver.connect)
def testMyNextTestCase(self):
# blah blah
信息我几个问题:
- 是否正确? :)
- 这是正确的方法来尝试和模拟一个对象,在我正在测试的类内实例化?
- 可以在setUp上调用@patch装饰器,或者这会导致奇怪的副作用吗?
- 有没有办法让模拟来提升ldap.INVALID_CREDENTIALS异常而不必将异常导入到我的测试用例文件中?
- 我应该使用patch.object()来代替吗?如果是这样,怎么办?
谢谢。
参见:26.5.3.4. Applying the same patch to every test method
更有意义设立该修补上的设置这样,如果你想为所有的测试方法进行修补。
对于Python3之前的模拟(位于http://www.voidspace.org.uk/python/mock/上),请参阅[在每种测试方法上应用相同的补丁](http://www.voidspace.org.uk/python/mock/examples.html#applying-the-same-patch-to-every-test-method)。 – musiphil 2015-03-17 21:59:02
我刚刚遇到了一个问题,我在TestCase类上有一个类级别的模拟,并假定它在'setUp()'方法中进行调用时已经存在。不是这种情况; class级别的mock不能及时应用于'setUp()'中。我解决了这个问题,而是创建了一个我在所有测试中使用的帮助器方法。不知道这是最好的方法,但它的工作原理。 – berto 2016-03-31 15:05:57
@berto如果您在答复中扩充您的评论,我认为这会有所帮助。这是一个不同的,可能比其他人更容易的解决方案。 – KobeJohn 2016-11-17 03:47:01
如果你有很多的补丁应用,并且希望他们申请到的东西在设置方法初始化也试试这个:
def setUp(self):
self.patches = {
"sut.BaseTestRunner._acquire_slot": mock.Mock(),
"sut.GetResource": mock.Mock(spec=GetResource),
"sut.models": mock.Mock(spec=models),
"sut.DbApi": make_db_api_mock()
}
self.applied_patches = [mock.patch(patch, data) for patch, data in self.patches.items()]
[patch.apply for patch in self.applied_patches]
.
. rest of setup
.
def tearDown(self):
patch.stop_all()
考虑在'tearDown()'中使用'patch.stop_all()'。 – 2015-03-30 15:42:09
我试过了 - 似乎apply_patches也需要启动。考虑一下这样的一行:'在self.applied_patches:patch.start()'中使用补丁 – F1Rumors 2016-04-04 18:44:47
我会回答你的问题开始,然后我就给如何与patch()
和setUp()
进行交互的详细示例。
- 我不认为它看起来不错,详情见答案#3。
- 是的,实际调用补丁看起来应该嘲笑你想要的对象。
- 不,你几乎从不想在
setUp()
上使用@patch()
修饰器。你很幸运,因为该对象是在setUp()
中创建的,并且在测试方法中永远不会创建。 - 我不知道有什么办法可以让一个模拟对象抛出一个异常,而不会将这个异常导入到测试用例文件中。
- 我在这里看不到有任何需要
patch.object()
。它只是让你修补一个对象的属性,而不是将目标指定为一个字符串。
要扩大答案#3,问题在于patch()
装饰器只适用于装饰功能正在运行。一旦setUp()
返回,修补程序将被删除。在你的情况下,这是有效的,但我敢打赌它会让看着这个测试的人感到困惑。如果你真的只想在setUp()
期间发生这个补丁,我会建议使用with
声明来明确补丁将被删除。
以下示例有两个测试用例。 TestPatchAsDecorator
显示装饰课程将在测试方法期间应用该补丁,但不在setUp()
期间。 TestPatchInSetUp
显示了如何应用该修补程序,以便在setUp()
和测试方法中都可以使用该修补程序。调用self.addCleanUp()
可确保在tearDown()
期间修补程序将被删除。
import unittest
from mock import patch
@patch('__builtin__.sum', return_value=99)
class TestPatchAsDecorator(unittest.TestCase):
def setUp(self):
s = sum([1, 2, 3])
self.assertEqual(6, s)
def test_sum(self, mock_sum):
s1 = sum([1, 2, 3])
mock_sum.return_value = 42
s2 = sum([1, 2, 3])
self.assertEqual(99, s1)
self.assertEqual(42, s2)
class TestPatchInSetUp(unittest.TestCase):
def setUp(self):
patcher = patch('__builtin__.sum', return_value=99)
self.mock_sum = patcher.start()
self.addCleanup(patcher.stop)
s = sum([1, 2, 3])
self.assertEqual(99, s)
def test_sum(self):
s1 = sum([1, 2, 3])
self.mock_sum.return_value = 42
s2 = sum([1, 2, 3])
self.assertEqual(99, s1)
self.assertEqual(42, s2)
1-3)似乎没什么问题... 4)'进口ldap'代替,并设置'side_effect = ldap.INVALID_CREDENTIALS'? – Chris 2013-04-04 22:28:38
您可以随时进行相同的测试,但只能使用自己制作的简单对象... – shackra 2014-05-11 20:09:36