【问题标题】:Pydantic (BaseModel) - How to mock (pytest/unittest/mockito)?Pydantic (BaseModel) - 如何模拟 (pytest/unittest/mockito)?
【发布时间】:2022-06-14 09:44:28
【问题描述】:

我正在进行单元测试(基本上使用 pytest/unittest/mockito),我需要模拟使用 Pydantic (BaseModel) 实现的类的实例化。显然,如果不传递有效的有效数据,就不可能在这些情况下模拟一个类。我不能使用“ANY()”,因为会发生错误。有什么方法可以模拟这个类而不必使用有效数据作为参数?

注意:显然问题是因为正在使用 Pydantic。

我在互联网上做了很多研究,但没有运气???? ...有什么想法吗?

以下是我在测试中以非常简化的方式使用的代码...

pydantic_class.py - Pydantic (BaseModel) 类

from pydantic import BaseModel
from some.path.sometypea import SomeTypeA
from some.path.sometypeb import SomeTypeB


class PydanticBaseModel(BaseModel):
    someInt: int
    someStr: str
    someTypeA: SomeTypeA
    someTypeB: SomeTypeB

code_to_test.py - 测试代码

from some.path.pydantic_class import PydanticBaseModel


class ClassToTest():
    def test_method(self)
        pydantic_base_model = PydanticBaseModel(
            someInt=0,
            someStr="value",
            someTypeA=<SomeTypeAObj>,
            someTypeB=<SomeTypeBObj>
        )
        [...]

test_code.py - 测试代码

import unittest
from mockito import ANY, when


class SomeTypeTest(unittest.TestCase):
    def test_sometype_method(self):
        when(PydanticBaseModel(
            someInt=ANY(),
            someStr=ANY(),
            someTypeA=ANY(),
            someTypeB=ANY()
        )).thenReturn(None)
        [...]

测试输出(简体)

(test-project) [username@username-pc test-project]$ pytest -sv ./test_code.py
=================================================================== test session starts ====================================================================

[...]

_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

>   ???
E   pydantic.error_wrappers.ValidationError: 4 validation errors for PydanticBaseModel
E   someInt
E     value is not a valid integer (type=type_error.integer)
E   someStr
E     str type expected (type=type_error.str)
E   someTypeA
E     value is not a valid dict (type=type_error.dict)
E   someTypeA
E     value is not a valid dict (type=type_error.dict)

pydantic/main.py:338: ValidationError
================================================================= short test summary info ==================================================================
FAILED test_code.py::SimulacaoComboTest::test_sometype_method - pydantic.error_wrappers.ValidationError: 2 validat...
==================================================================== 1 failed in 0.94s =====================================================================

谢谢! ????

【问题讨论】:

    标签: mockito pytest python-unittest pydantic python-unittest.mock


    【解决方案1】:

    我不熟悉mockito,但您似乎同时滥用了用于monkey-patching 对象的when 和用于测试的ANY() em> 值,不用于赋值。

    mockito walk-through 展示了如何使用when 函数。当您需要模拟某些功能时,您可以使用它。

    ANY 函数是一个匹配器:它用于匹配例如函数调用中的参数。

    以下是两者的实际示例:

    如果您希望os.path.exists 始终返回True,无论路径如何,都可以调用:

    >>> when(os.path).exists(ANY).thenReturn(True)
    >>> os.path.exists("/path/example")
    True
    >>> os.path.exists("/another/example")
    True
    

    这里,参数列表中的ANY 匹配任何 参数,因此os.path.exists 将返回True,无论我们如何调用它。

    如果我们只希望它为特定路径返回True,我们会改为:

    >>> when(os.path).exists(ANY).thenReturn(True)
    >>> when(os.path).exists("/another/example").thenReturn(True)
    >>> os.path.exists("/path/example")
    False
    >>> os.path.exists('/another/example')
    True
    

    对于您正在做的事情,您似乎不需要这些结构中的任何一个。如果您想测试“当我创建PydanticBaseModel 时,返回的对象与我在构造它时使用的值相同”,那么您可以这样写:

    import unittest
    
    from model import PydanticBaseModel, SomeTypeA, SomeTypeB
    
    
    class SomeTypeTest(unittest.TestCase):
        def test_sometype_method(self):
            expectedTypeA = SomeTypeA()
            expectedTypeB = SomeTypeB()
    
            expected = {
                "someInt": 0,
                "someStr": "",
                "someTypeA": expectedTypeA,
                "someTypeB": expectedTypeB,
            }
    
            model = PydanticBaseModel(
                someInt=0,
                someStr="",
                someTypeA=expectedTypeA,
                someTypeB=expectedTypeB,
            )
    
            assert model.dict() == expected
    

    【讨论】:

    • 老兄,这不是解释,而是教训?!如果我只是想绕过“PydanticBaseModel”的执行,那么它的调用不会引发错误怎么办?也就是说,我不关心“PydanticBaseModel”是否被执行,只关心它在测试场景中的执行过程中不会产生错误。 ??
    • 如果您不关心值,只需调用PydanticBaseModel(...) 而不将值分配给任何东西,也不需要assert-ing 任何东西。要么测试通过(函数完成且没有错误),要么引发异常,导致测试失败。
    • 请注意,我在对“PydanticBaseModel”(test_code.py)的调用中添加了.thenReturn(None),以更好地解释我想要什么。也就是说,我不希望这个类被实例化,因为它在调用中的参数是无效的。我只需要“实例”来返回“无”。 ??
    【解决方案2】:

    好的,朋友们!

    我发现了 3 种不同的方法来模拟 Pydantic (BaseModel) 类的实例化(构造)。

    注意:我不知道这些是否是解决问题的最佳方法,或者它们是否正确。所以我请你发表评论!

    方法 1(我认为最好的?)

    import unittest
    from unittest import mock
    
    
    class SomeTypeTest(unittest.TestCase):
    
        @mock.patch("some.path.pydantic_class.PydanticBaseModel.__init__")
        def test_sometype_method(self, pydantic_base_model):
            pydantic_base_model.return_value = None
            <SOME_PATCH_DEPENDENT_CODE>
            [...]
    

    方法 2

    import unittest
    
    from unittest.mock import patch
    from some.path.pydantic_class import PydanticBaseModel
    
    class SomeTypeTest(unittest.TestCase):
    
        def test_sometype_method(self):
            with patch.object(PydanticBaseModel, "__init__", return_value=None):
                <SOME_PATCH_DEPENDENT_CODE>
    
            [...]
    

    方法 3

    import unittest
    from unittest.mock import patch
    
    
    class SomeTypeTest(unittest.TestCase):
    
        def test_sometype_method(self):
            patcher = patch("some.path.pydantic_class.PydanticBaseModel.__init__", return_value=None)
            patcher.start()
            self.addCleanup(patcher.stop)
            <SOME_PATCH_DEPENDENT_CODE>
            [...]
    

    注意:使用“addCleanup”,您不再需要保留对修补程序对象的引用。
    参考:https://docs.python.org/3/library/unittest.mock.html#patch-methods-start-and-stop


    另外:我继续坚持 Mockito 方法...

    when(PydanticBaseModel).thenReturn(None)
    

    ...失败是因为“PydanticBaseModel”类使用了 Pydantic (BaseModel),这似乎是 Mockito 无法处理的。


    谢谢! ?


    更新 (20220613.2232): 虽然所有答案都“有效”地模拟了课程,但可能正确的答案是这样的...

    import unittest
    from unittest.mock import patch
    from some.path.pydantic_class import PydanticBaseModel
    
    
    class SomeTypeTest(unittest.TestCase):
    
        def test_sometype_method(self):
            patcher = patch("some.path.pydantic_class.PydanticBaseModel.__new__", return_value=PydanticBaseModel(**{<INITIALIZATION_DICT_CONTENT>}))
            patcher.start()
            self.addCleanup(patcher.stop)
            <SOME_PATCH_DEPENDENT_CODE>
            [...]
    

    ...因为这样我们可以在我们的控制下定义一个类实例返回(而不仅仅是“None”)。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-02-22
      • 2020-09-12
      • 1970-01-01
      • 2021-12-01
      • 2021-05-27
      • 2022-10-07
      • 1970-01-01
      相关资源
      最近更新 更多