如何使用pytest-mock或magicmock模拟导入的对象

时间:2019-10-14 12:46:31

标签: python unit-testing mocking pytest pytest-mock

我正在尝试了解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

3 个答案:

答案 0 :(得分:3)

我相信您不应该在真实的数据库上测试案例,因为如果您使用的是外部依赖项,那么它就不再是单元测试了。

存在to specify return-value并为MockMagicMock对象自定义(different return values on each iteration even)的可能性。

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 **的路径(我想是在每个测试中使用不同的路径),而不是在何处定义。

答案 1 :(得分: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(...)

请注意,您没有修补actual source of db

用于其他用例

  

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

您同样会按照上面链接的提示进行操作:

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

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


此外,请参见an example how to pach mongo db.

有关Flask应用程序的测试,请参见the documentation of flask。还有this is a nice explanation as well, and uses DB connections

作为一般提示,例如提到的@MikeMajara-将您的代码更多地分成较小的功能,这些功能也易于测试。按照TDD的传统:先编写测试,然后实施,然后进行重构(尤其是DRY!)

答案 2 :(得分:1)

关注点分离。构建仅做一件事的方法。如果要使用TDD,则更多。在您的示例中,some_func2不止一项。您可以重构如下:

rm -rf node_modules && npm i

使用这种方法,您可以轻松地分别测试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。这样可以提高可读性,避免错误,并有助于在某些时候出现这些错误。


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

要填充,我将简化:您知道要测试的条件,并且要检查给定对象的结果是否为预期的结果。只需构建一个check_condition_on_object即可返回test_object_provider.py的已知案例。无需使事情变得更复杂。

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