如何在python中使用unittest setUp正确使用mock

sjm*_*jmh 56 python unit-testing mocking

在我尝试学习TDD时,尝试学习单元测试并使用mock与python.慢慢地掌握它,但不确定我是否正确地这样做.预警:我正在坚持使用python 2.4,因为供应商API是预先编译的2.4 pyc文件,所以我使用模拟0.8.0和unittest(不是unittest2)

给出'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
Run Code Online (Sandbox Code Playgroud)

现在在我的测试用例文件'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
Run Code Online (Sandbox Code Playgroud)

引出了几个问题:

  1. 那看起来不错吗?:)
  2. 这是尝试和模拟在我正在测试的类中实例化的对象的正确方法吗?
  3. 可以在setUp上调用@patch装饰器,还是会导致奇怪的副作用?
  4. 反正是否有模拟提升ldap.INVALID_CREDENTIALS异常而不必将异常导入我的testcase文件?
  5. 我应该使用patch.object()而不是,如果是这样,怎么样?

谢谢.

joo*_*oks 61

您可以将其patch()用作类装饰器,而不仅仅是作为函数装饰器.然后,您可以像以前一样传入模拟函数:

@patch('mymodule.SomeClass')
class MyTest(TestCase):

    def test_one(self, MockSomeClass):
        self.assertIs(mymodule.SomeClass, MockSomeClass)
Run Code Online (Sandbox Code Playgroud)

见:26.5.3.4.将相同的补丁应用于每个测试方法(也列出替代方案)

如果您希望对所有测试方法进行修补,那么在setUp上以这种方式设置修补程序更有意义.

  • 我刚遇到一个问题,我在TestCase类上有一个类级别的模拟,并假设它在`setUp()`方法中调用时已经存在.不是这种情况; 类set-mocks没有及时应用于`setUp()`.我解决了这个问题,相反,创建了一个我在所有测试中使用的辅助方法.不确定这是最好的方法,但它确实有效. (5认同)
  • 对于Python3之前的Mock(托管在http://www.voidspace.org.uk/python/mock/上),请参阅[对每种测试方法应用相同的补丁](http://www.voidspace. org.uk/python/mock/examples.html#applying-the-same-patch-to-every-test-method). (2认同)

Don*_*kby 12

我将首先回答您的问题,然后我将详细介绍如何patch()setUp()互动.

  1. 我不认为它看起来是正确的,请参阅此列表中问题#3的答案以获取详细信息.
  2. 是的,实际调用补丁看起来应该模拟你想要的对象.
  3. 不,你几乎从不想使用@patch()装饰器setUp().你很幸运,因为setUp()在测试方法中创建了对象并且永远不会创建对象.
  4. 我不知道如何使模拟对象引发异常而不将该异常导入测试用例文件.
  5. 我认为没有必要在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)
Run Code Online (Sandbox Code Playgroud)


Dan*_*ple 10

如果要应用许多补丁并且希望它们应用于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()
Run Code Online (Sandbox Code Playgroud)

  • 考虑在`tearDown()`中使用`patch.stop_all()`. (3认同)
  • 公平地说 - 我现在使用"self.addCleanup(patch)"方法.是时候更新这个答案了. (3认同)
  • ```self.addCleanup(patch.stopall)``` (2认同)

Kur*_*eek 6

我想指出一种可接受的答案的变体,其中将一个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
Run Code Online (Sandbox Code Playgroud)

请注意,在这种情况下,不再需要MockSomeClass向每个测试方法中添加第二个参数,这可以节省大量代码重复。

对此的说明可以在https://docs.python.org/3/library/unittest.mock.html#patch中找到:

如果将if patch()用作装饰器,并且省略new,则将创建的模拟作为额外的参数传递给装饰函数。

首先,答案省略new,但是将其包括在内可能很方便。