代码之家  ›  专栏  ›  技术社区  ›  lemi57ssss

如何模拟iInstance测试中使用的类的补丁?

  •  5
  • lemi57ssss  · 技术社区  · 6 年前

    我想测试功能 is_myclass 。请帮助我了解如何编写成功的测试。

    def is_myclass(obj):
        """This absurd stub is a simplified version of the production code."""
        isinstance(obj, MyClass)
        MyClass()
    

    文档

    Python文档用于unittest。mock演示了三种解决 isinstance 问题:

    • 设置 spec 实数类的参数。
    • 将实际类分配给 __class__ 属性
    • 使用 规格 在真实班级的补丁中。

    __类别__

    通常 __类别__ 对象的属性将返回其类型。对于具有等级库的模拟对象, __类别__ 返回spec类。这允许模拟对象通过其替换/伪装为的对象的isinstance()测试:

    >>> mock = Mock(spec=3)
    >>> isinstance(mock, int)
    True
    

    __类别__ 可分配给,这允许模拟通过 isinstance() 在不强制使用等级库的情况下进行检查:

    >>> mock = Mock()
    >>> mock.__class__ = dict
    >>> isinstance(mock, dict)
    True
    

    […]

    如果您使用 规格 spec_set patch() 正在替换类,则创建的模拟的返回值将具有相同的规范。

    >>> Original = Class
    >>> patcher = patch('__main__.Class', spec=True)
    >>> MockClass = patcher.start()
    >>> instance = MockClass()
    >>> assert isinstance(instance, Original)
    >>> patcher.stop()
    

    测试

    我已经编写了五个测试,每个测试都试图首先复制三个解决方案中的每一个,然后对目标代码进行实际测试。典型的模式是 assert isinstance 然后拨打 is\U myclass

    所有测试均失败。

    测试1

    这是文档中提供的示例的副本,供使用 规格 信息技术 与文档的不同之处在于使用 spec=<class> 而不是 spec=<instance> .它通过了 本地断言测试,但调用 is\U myclass 失败的原因是 MyClass 不会被嘲笑。

    这相当于米歇尔·达米科对中类似问题的回答 isinstance and Mocking

    测试2

    这是测试1的修补等效物。这个 规格 参数无法设置 __类别__ 而本地 断言isinstance

    测试3

    这是文档中提供的示例的副本,供使用 __类别__ .它通过了 本地断言测试,但调用 is\U myclass 失败的原因是 类名 不会被嘲笑。

    测试4

    这是测试3的修补等效物。分配给 __类别__ 是否设置 __类别__ 被嘲笑的人 类名 但这不会改变其类型,因此测试无法通过本地 断言isinstance

    测试5

    这是使用 规格 打电话给patch。它通过了本地断言测试,但仅通过访问MyClass的本地副本。因为此局部变量未在中使用 is\U myclass 呼叫失败。

    密码

    这段代码是作为一个独立的测试模块编写的,打算在PyCharm IDE中运行。您可能需要修改它以在其他测试环境中运行。

    模块temp2。py公司

    import unittest
    import unittest.mock as mock
    
    
    class WrongCodeTested(Exception):
        pass
    
    
    class MyClass:
        def __init__(self):
            """This is a simplified version of a production class which must be mocked for unittesting."""
            raise WrongCodeTested('Testing code in MyClass.__init__')
    
    
    def is_myclass(obj):
        """This absurd stub is a simplified version of the production code."""
        isinstance(obj, MyClass)
        MyClass()
    
    
    class ExamplesFromDocs(unittest.TestCase):
        def test_1_spec(self):
            obj = mock.Mock(spec=MyClass)
            print(type(MyClass))  # <class 'type'>
            assert isinstance(obj, MyClass)  # Local assert test passes
            is_myclass(obj)  # Fail: MyClass instantiated
    
    
        def test_2_spec_patch(self):
            with mock.patch('temp2.MyClass', spec=True) as mock_myclass:
                obj = mock_myclass()
                print(type(mock_myclass))  # <class 'unittest.mock.MagicMock'>
                print(type(MyClass))  # <class 'unittest.mock.MagicMock'>
                assert isinstance(obj, MyClass)  # Local assert test fails
    
        def test_3__class__(self):
            obj = mock.Mock()
            obj.__class__ = MyClass
            print(type(MyClass))  # <class 'type'>
            isinstance(obj, MyClass)  # Local assert test passes
            is_myclass(obj)  # Fail: MyClass instantiated
    
        def test_4__class__patch(self):
            Original = MyClass
            with mock.patch('temp2.MyClass') as mock_myclass:
                mock_myclass.__class__ = Original
                obj = mock_myclass()
                obj.__class__ = Original
                print(MyClass.__class__)  # <class 'temp2.MyClass'>
                print(type(MyClass))  # <class 'unittest.mock.MagicMock'>
                assert isinstance(obj, MyClass)  # Local assert test fails
    
        def test_5_patch_with_spec(self):
            Original = MyClass
            p = mock.patch('temp2.MyClass', spec=True)
            MockMyClass = p.start()
            obj = MockMyClass()
            print(type(Original))  # <class 'type'>
            print(type(MyClass))  # <class 'unittest.mock.MagicMock'>
            print(type(MockMyClass))  # <class 'unittest.mock.MagicMock'>
            assert isinstance(obj, Original)  # Local assert test passes
            is_myclass(obj)  # Fail: Bad type for MyClass
    
    2 回复  |  直到 6 年前
        1
  •  6
  •   Martijn Pieters    6 年前

    你不能嘲笑 isinstance() ,否。您发现的文档涉及将模拟作为 第一 参数通过测试。如果你想产生一些可以接受的东西作为 isinstance() ,实际上你必须 类型 ,而不是实例(并且模拟始终是实例)。

    可以使用子类代替 MyClass 相反,这肯定会过去 __new__ 方法允许您更改尝试调用它以创建实例时返回的内容:

    class MockedSubClass(MyClass):
        def __new__(cls, *args, **kwargs):
            return mock.Mock(spec=cls)  # produce a mocked instance when called
    

    并将其修补到:

    mock.patch('temp2.MyClass', new=MockedSubClass)
    

    并使用该类的实例作为模拟:

    instance = mock.Mock(spec=MockedSubClass)
    

    或者,这是 简单得多 ,只需使用 Mock 作为一个班级 obj 成为一名 嘲弄 实例:

    with mock.patch('temp2.MyClass', new=mock.Mock) as mocked_class:
        is_myclass(mocked_class())
    

    无论哪种方式,您的测试都会通过:

    >>> with mock.patch('temp2.MyClass', new=MockedSubClass) as mocked_class:
    ...     instance = mock.Mock(spec=MockedSubClass)
    ...     assert isinstance(instance, mocked_class)
    ...     is_myclass(instance)
    ...
    >>> # no exceptions raised!
    ...
    >>> with mock.patch('temp2.MyClass', new=mock.Mock) as mocked_class:
    ...     is_myclass(mocked_class())
    ...
    >>> # no exceptions raised!
    ...
    

    对于您的特定测试,以下是它们失败的原因:

    1. 你从来没有嘲笑过 类名 ,它仍然引用原始类。的第一行 is_myclass() 成功,但第二行使用原始 类名 而且是陷阱。
    2. 类名 替换为 mock.Mock 实例,而不是实际类型,因此 isinstance() 引发 TypeError: isinstance() arg 2 must be a type or tuple of types 例外
    3. 与1失败的方式完全相同, 类名 机智地离开,被诱杀。
    4. 以与2相同的方式失败。 __class__ 属性是否仅对 实例 。类对象不使用 __类别__ 属性,您仍然有一个实例而不是类,并且 isinstance() 引发类型错误。
    5. 本质上与4完全相同,只是您手动启动补丁程序,而不是让上下文管理器来处理它,并且您使用 isinstance(obj, Original) 检查实例,这样就不会出现类型错误。而是在中触发类型错误 is\u myclass()
        2
  •  0
  •   Nathaniel Ruiz    4 年前

    @Martijn Pieters有一个很好的答案,我只是想补充一下我是如何与装饰师一起完成这一点的:

    import temp2
    
    class MockedMyClass:
        pass
    
    class MockedMySubClass(MockedMyClass):
        pass
    
    @patch("temp2.MyClass", new=MockedMyClass)
    def test_is_subclass(self):
        assert issubclass(MockedMySubClass, temp2.MyClass)
    

    注: 即使使用了decorator,测试也不需要任何额外的参数。