如何通过unittest setUp在python中正确使用mock
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/15821465/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How to properly use mock in python with unittest setUp
提问by sjmh
In my attempt to learn TDD, trying to learn unit testing and using mock with python. Slowly getting the hang of it, but unsure if I'm doing this correctly. Forewarned: I'm stucking using python 2.4 because the vendor API's come as pre-compiled 2.4 pyc files, so I'm using mock 0.8.0 and unittest ( not unittest2 )
在我尝试学习 TDD,尝试学习单元测试和使用 python 模拟。慢慢掌握它的窍门,但不确定我是否正确地做到了这一点。预先警告:我一直在使用 python 2.4,因为供应商 API 是作为预编译的 2.4 pyc 文件出现的,所以我使用的是模拟 0.8.0 和 unittest (不是 unittest2 )
Given this example code in 'mymodule.py'
鉴于'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
Now in my test case file 'test_myclass.py', I want to mock the ldap object out. ldap.initialize returns the ldap.ldapobject.SimpleLDAPObject, so I figured that'd be the method I'd have to mock out.
现在在我的测试用例文件“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
Leads me to a couple of questions:
让我想到几个问题:
- Does that look right? :)
- Is that the proper way to try and mock an object that gets instantiated within the class I'm testing?
- Is it ok to be calling the @patch decorator on setUp or is this going to cause weird side effects?
- Is there anyway to get mock to raise the ldap.INVALID_CREDENTIALS exception without having to import the exception into my testcase file?
- Should I be using patch.object() instead and if so, how?
- 看起来对吗?:)
- 这是尝试模拟在我正在测试的类中实例化的对象的正确方法吗?
- 可以在 setUp 上调用 @patch 装饰器还是这会导致奇怪的副作用?
- 无论如何,是否可以模拟引发 ldap.INVALID_CREDENTIALS 异常而不必将异常导入我的测试用例文件?
- 我应该改用 patch.object() 吗?如果是这样,如何使用?
Thanks.
谢谢。
采纳答案by jooks
You can use patch()as a class decorator, not just as a function decorator. You can then pass in the mocked function as before:
您可以patch()用作类装饰器,而不仅仅是用作函数装饰器。然后,您可以像以前一样传入模拟函数:
@patch('mymodule.SomeClass')
class MyTest(TestCase):
def test_one(self, MockSomeClass):
self.assertIs(mymodule.SomeClass, MockSomeClass)
See: 26.5.3.4. Applying the same patch to every test method(which also lists alternatives)
见:26.5.3.4。对每个测试方法应用相同的补丁(也列出了替代方法)
It makes more sense to set up the patcher this way on setUp if you want the patching to be done for all the test methods.
如果您希望对所有测试方法进行修补,那么在 setUp 上以这种方式设置修补程序更有意义。
回答by Danny Staple
If you have many patches to apply and you want them to apply to things initialised in the setUp methods too try this:
如果您有许多补丁要应用,并且您希望它们应用于在 setUp 方法中初始化的内容,请尝试以下操作:
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.stopall()
回答by Don Kirkby
I'll start by answering your questions, and then I'll give a detailed example of how patch()and setUp()interact.
我会回答你的问题开始,然后我会给出怎样一个详细的例子patch()和setUp()互动。
- I don't think it looks right, see my answer to question #3 in this list for details.
- Yes, the actual call to patch looks like it should mock the object you want.
- No, you almost never want to use the
@patch()decorator onsetUp(). You got lucky, because the object is created insetUp()and never gets created during the test method. - I don't know of any way to make a mock object raise an exception without importing that exception into your test case file.
- I don't see any need for
patch.object()here. It just lets you patch attributes of an object instead of specifying the target as a string.
- 我认为它看起来不对,有关详细信息,请参阅我对此列表中问题 #3 的回答。
- 是的,对 patch 的实际调用看起来应该模拟您想要的对象。
- 不,您几乎从不想
@patch()在setUp(). 你很幸运,因为对象是在setUp()测试方法中创建的,并且永远不会在测试方法中创建。 - 我不知道有什么方法可以让模拟对象引发异常而不将该异常导入您的测试用例文件。
- 我认为
patch.object()这里没有任何必要。它只是让您修补对象的属性,而不是将目标指定为字符串。
To expand on my answer to question #3, the problem is that the patch()decorator only applies while the decorated function is running. As soon as setUp()returns, the patch is removed. In your case, that works, but I bet it would confuse someone looking at this test. If you really only want the patch to happen during setUp(), I would suggest using the withstatement to make it obvious that the patch is going to be removed.
扩展我对问题 3 的回答,问题是patch()装饰器仅在被装饰的函数运行时才适用。一旦setUp()返回,补丁就会被删除。在你的情况下,这是有效的,但我敢打赌,这会让看这个测试的人感到困惑。如果您真的只希望补丁在 期间发生setUp(),我建议使用with声明来明确表示补丁将被删除。
The following example has two test cases. TestPatchAsDecoratorshows that decorating the class will apply the patch during the test method, but not during setUp(). TestPatchInSetUpshows how you can apply the patch so that it's in place during both setUp()and the test method. Calling self.addCleanUp()makes sure that the patch will be removed during tearDown().
以下示例有两个测试用例。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)
回答by Kurt Peek
I'd like to point out a variation of the accepted answer in which a newargument is passed to the patch()decorator:
我想指出一个已接受答案的变体,其中将new参数传递给patch()装饰器:
from unittest.mock import patch, Mock
MockSomeClass = Mock()
@patch('mymodule.SomeClass', new=MockSomeClass)
class MyTest(TestCase):
def test_one(self):
# Do your test here
Note that in this case, it is no longer necessary to add the second argument, MockSomeClass, to every test method, which can save a lot of code repetition.
请注意,在这种情况下,不再需要MockSomeClass在每个测试方法中添加第二个参数 , ,这样可以节省大量代码重复。
An explanation of this can be found at https://docs.python.org/3/library/unittest.mock.html#patch:
对此的解释可以在https://docs.python.org/3/library/unittest.mock.html#patch找到:
If
patch()is used as a decorator and newis omitted, the created mock is passed in as an extra argument to the decorated function.
如果
patch()用作装饰器并省略new,则创建的模拟将作为额外参数传入装饰函数。
The answers above all omit new, but it can be convenient to include it.
答案首先省略了new,但包含它会很方便。
回答by Karpad
You can create a patched inner function and call it from setUp.
您可以创建一个修补的内部函数并从setUp.
If your original setUpfunction is:
如果您的原始setUp功能是:
def setUp(self):
some_work()
Then you can patch it by changing it to:
然后您可以通过将其更改为:
def setUp(self):
@patch(...)
def mocked_func():
some_work()
mocked_func()

