7

我正在尝试了解这些mock/monkeypatch/pytest-mock功能。

让我知道这是否可能。如果不能,请建议我如何测试此代码。

我的代码结构:

/
./app
../__init__.py
../some_module1
.../__init__.py
../some_module2
.../__init__.py
./tests
../test_db.py

/app/__init__.py是我的应用程序(如果有帮助的话是 Flask 应用程序)与初始化数据库连接对象到 MongoDB 数据库的地方:

# ...

def create_app():
  # ...
  return app

db_conn = DB()

some_module1some_module导入对象并将其db_conn用作其功能的一部分:

## some_module1/__init__.py
from app import db_conn

...
db = db_conn.db_name2.db_collection2

def some_func1():
    data = db.find()
    # check and do something with data
    return boolean_result

...

## some_module2/__init__.py
from app import db_conn

...
db = db_conn.db_name1.db_collection1

def some_func2():
    data = db.find()
    # check and do something with data
    return boolean_result
...

在我的测试中,我想根据从数据库接收到的数据来测试我的代码是否正常工作。我想模拟数据库,更具体地说是db_conn对象,因为我不想使用真正的数据库(这将是设置环境和维护它的大量工作)。

关于如何模仿的任何建议db_conn

我一直在探索pytest-mockmagicmock但我不知道也不知道如何db_conn在我的测试中模拟。

4

3 回答 3

3

我相信你没有在真实数据库上测试用例是对的,因为如果你使用外部依赖项,它就不再是单元测试了。

可以为or对象指定return-value和自定义它(甚至每次迭代的不同返回值)。MockMagicMock

from unittest.mock import Mock, patch 

from app import db_conn


@patch('app.db_conn.find')
def test_some_func1(db_con_mock):
    ...
    assert ...

请记住,在每个patch您应该指定的导入路径db_conn-使用 **的路径db_conn(我假设它在每个测试中都是不同的路径),而不是定义它的位置。

于 2019-10-29T12:11:35.967 回答
2

要回答初始问题“如何使用 pytest-mock 或 magicmock 模拟导入的对象”,您可以执行以下操作:

from unittest import mock  # because unittest's mock works great with pytest

def test_some_func1():
    with mock.patch('some_module1.db', mock.MagicMock(return_value=...)) as magicmock:
        result = some_func1(...)
        assert ... e.g. different fields of magicmock
        assert expected == result

# or alternatively use annotations

@mock.patch('some_module2.db', mock.MagicMock(return_value=...))
def test_some_func2():
        result = some_func2(...)

请注意,您没有修补db 的实际来源

对于您的其他用例

我想模拟数据库(使用 mongo 数据库),更具体地说是“db_conn”对象

您同样遵循上面链接的提示:

mock.patch('some_module1.db_conn', mock.MagicMock(return_value=...))

鉴于此,您会在测试中注意到db`db = db_conn.db_name2.db_collection2' 将创建另一个模拟对象。对该对象的调用也将被记录。通过这种方式,您还可以跟踪调用和值分配的历史记录。


此外,请参阅如何修补 mongo db 的示例。

有关 Flask 应用程序的测试,请参阅flask 的文档。这也是一个很好的解释,并使用 DB 连接

作为一般提示,就像@MikeMajara 提到的那样 - 将您的代码更多地分成更易于测试的更小的函数。TDD 的传统:先编写测试,后实施,然后重构(尤其是 DRY!)

于 2019-10-29T10:59:41.143 回答
1

关注点分离。构建只做一件事的方法。如果您要使用 TDD,那就更是如此。在您的示例中, some_func2 不止一个。你可以重构如下:

def get_object_from_db():
    return db.find()

def check_condition_on_object(obj):
    check something to do with object
    return true or false

def some_func2():
   obj = get_object_from_db()
   check_condition_on_object(obj)

使用这种方法,您可以轻松地get_object_from_db单独进行测试check_condition_on_object。这将提高可读性,避免错误,并在它们出现时帮助检测它们。


关于“模拟导入的对象”。您可能正在尝试使用一个用于比您的更高级案例的库来模拟一个对象。这些库为您提供了一堆您可能不需要的开箱即用的测试环境方法。从外观上看,您只想用模拟数据填充对象,和/或与模拟的 db_connection 实例交互。所以...

要填充,我会简化:您知道要测试的条件,并且要检查给定对象的结果是否是预期的。只需为自己构建一个test_object_provider.py返回已知案例的true|false. 没有必要让事情变得更复杂。

要使用虚假的 MongoDB 连接,您可以尝试使用mongomock。(尽管理想情况下您会在单独的测试中使用真实实例测试 mongo)。

于 2019-10-29T10:15:17.310 回答