如何在python中通过unittest设置正确使用mock

如何在python中通过unittest设置正确使用mock,python,unit-testing,mocking,Python,Unit Testing,Mocking,在我学习TDD的尝试中,我尝试学习单元测试和使用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

在我学习TDD的尝试中,我尝试学习单元测试和使用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
现在在我的测试用例文件“test_myclass.py”中,我想模拟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
让我想到几个问题:

  • 看起来对吗?:)
  • 这是尝试和模拟在我测试的类中实例化的对象的正确方法吗
  • 在安装程序中调用@patch decorator可以吗?还是这会导致奇怪的副作用
  • 是否仍然可以让mock引发ldap.INVALID_CREDENTIALS异常,而不必将异常导入我的testcase文件
  • 我应该改用patch.object()吗?如果应该,如何使用
  • 谢谢。

    您可以用作类装饰器,而不仅仅是函数装饰器。然后可以像前面一样传入模拟函数:

    @patch('mymodule.SomeClass')
    class MyTest(TestCase):
    
        def test_one(self, MockSomeClass):
            self.assertIs(mymodule.SomeClass, MockSomeClass)
    
    参见:(其中还列出了备选方案)


    如果您希望对所有测试方法进行修补,则在设置中以这种方式设置修补程序更有意义。

    如果您有许多修补程序要应用,并且希望它们也应用于在设置方法中初始化的内容,请尝试以下方法:

    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()
    

    首先我将回答您的问题,然后我将给出一个关于
    patch()
    setUp()
    如何交互的详细示例

  • 我认为这看起来不对,请参阅我对该列表中问题3的回答以了解详细信息
  • 是的,对patch的实际调用看起来应该模拟您想要的对象
  • 不,您几乎不想在
    setUp()
    上使用
    @patch()
    装饰程序。您很幸运,因为对象是在
    setUp()
    中创建的,在测试方法期间从未创建过
  • 我不知道有什么方法可以让模拟对象在不将异常导入测试用例文件的情况下引发异常
  • 我认为这里不需要
    patch.object()
    。它只允许您修补对象的属性,而不是将目标指定为字符串
  • 为了进一步说明我对问题3的回答,问题是
    patch()
    decorator仅在修饰函数运行时应用。只要
    setUp()
    返回,补丁就会被删除。在您的情况下,这是可行的,但我敢打赌,这会让查看此测试的人感到困惑。如果您真的只希望修补程序在
    setUp()
    期间运行,我建议使用
    with
    语句来明确说明修补程序将被删除

    下面的示例有两个测试用例
    TestPatchAsDecorator
    显示修饰类将在测试方法期间应用补丁,但在
    setUp()
    期间不会应用补丁
    TestPatchInSetUp
    显示了如何应用修补程序,以便在
    setUp()
    和测试方法期间将其放置到位。调用
    self.addCleanUp()
    可确保在
    tearDown()
    期间删除修补程序


    我想指出一个已接受答案的变体,其中一个
    new
    参数被传递给
    patch()
    decorator:

    from unittest.mock import patch, Mock
    
    MockSomeClass = Mock()
    
    @patch('mymodule.SomeClass', new=MockSomeClass)
    class MyTest(TestCase):
        def test_one(self):
            # Do your test here
    
    注意,在这种情况下,不再需要向每个测试方法添加第二个参数,
    MockSomeClass
    ,这可以节省大量代码重复

    有关这方面的说明,请访问:

    如果
    patch()
    用作修饰符,并且省略了new,则创建的mock将作为额外参数传递给修饰函数


    以上所有答案都省略了new,但包含它会很方便。

    您可以创建一个修补的内部函数,并从
    设置中调用它

    如果您原来的
    设置
    功能是:

    def setUp(self):
        some_work()
    
    然后,您可以将其更改为:

    def setUp(self):
        @patch(...)
        def mocked_func():
            some_work()
    
        mocked_func()
    

    1-3)我觉得很好。。。4)<代码>导入LDAP < /代码>,并设置<代码> SIDOSIFECTION=LDAP.ValuIDIORITION >您可以总是进行相同的测试,但用自己制作的更简单的对象……考虑使用<代码>修补程序.StudioAlpor()/<代码> <代码> TurDub()/代码>。考虑一个行:<代码>在Self.Apple的修补程序:Pux.Stad()/代码>它的代码> STOBALL ,而不是<代码> StutsOLL 。公平地说,我现在使用“Audio.AddiCuffUp(补丁)”方法。是时候更新这个答案了。
    self.addCleanup(patch.stopall)
    我遇到了一个问题,我在TestCase类上有一个类级模拟,并假设在调用
    setUp()
    方法时它已经存在。事实并非如此;类级模拟未及时应用于
    setUp()
    中。相反,我通过创建我在所有测试中使用的helper方法解决了这个问题。我不确定这是最好的方法,但它是有效的。@berto如果你在回答中扩展你的评论,我认为这将是有帮助的。这是一个不同的解决方案,可能比这里的其他解决方案更简单。谢谢!这非常有帮助,尤其是当有许多模拟的函数类不需要特殊的返回值或类似值时。保持测试用例函数定义的干净。谢谢,这帮助我使我的代码更干净。我不需要在每个方法的顶部添加注释,因为我有很多模拟类。这是一个很好的例子,说明了为什么人们应该一直滚动到旧的StackOverflow问题上,以获得超出公认答案的更多见解!布拉沃:我想你应该给你提到的答案#3一个超链接,因为这样会根据他们得到的分数对答案进行排序。我明白你的意思,@ErdinEray,但我实际上是在说话
    def setUp(self):
        @patch(...)
        def mocked_func():
            some_work()
    
        mocked_func()