Pydantic (BaseModel) - 如何模拟 (pytest/unittest/mockito)?

问题描述 投票:0回答:3

我正在做单元测试(基本上使用 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
3个回答
2
投票

我不熟悉

mockito
,但您看起来像是在滥用
when
(用于 monkey-patching 对象)和
ANY()
(用于 testing 值),而不是分配。

mockito 演练 展示了如何使用

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
匹配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

1
投票

好的,朋友们!

我发现了 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


PLUS:我继续坚持Mockito方法......

when(PydanticBaseModel).thenReturn(None)

...失败,因为“PydanticBaseModel”类使用了PydanticBaseModel),这似乎是 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>
        [...]

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

注意: 请注意,我们现在使用

__new__
而不是
__init__
。因此,这可能是正确的答案!


0
投票

Pydantic 模型属性也可以通过使用带有 autospec 的

unittest.mock.patch
来模拟,例如在带有装饰器语法的 pytest 中:

from model import PydanticBaseModel, SomeTypeA
from unittest import mock


class TestPydanticBaseModel:

    @mock.patch('SomeTypeA', autospec=True)
    def test_with_some_type_a(self, mock_type_a):
        PydanticBaseModel(someTypeA=mock_type_a)
        

© www.soinside.com 2019 - 2024. All rights reserved.