简体   繁体   English

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

[英]How to mock a imported object with pytest-mock or magicmock

I am trying to understand the mock/monkeypatch/pytest-mock capabilities.我正在尝试了解mock/monkeypatch/pytest-mock功能。

Let me know if this is possible.让我知道这是否可能。 If not could you please suggest how I can test this code.如果不能,请建议我如何测试此代码。

My code structure:我的代码结构:

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

The /app/__init__.py is where my application (a Flask application if it helps) is started along with initializing a database connection object to a MongoDB database: /app/__init__.py是我的应用程序(如果有帮助,则为 Flask 应用程序)以及初始化数据库连接 object 到 MongoDB 数据库的位置:

# ...

def create_app():
  # ...
  return app

db_conn = DB()

The some_module1 and some_module import the db_conn object and use it as part of their functions: some_module1some_module导入db_conn object 并将其用作其功能的一部分:

## 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
...

In my tests, I want to test if my code works properly based on the data received from the database.在我的测试中,我想根据从数据库接收到的数据来测试我的代码是否正常工作。 I want to mock the database, more specifically the db_conn object since I don't want to use a real database (which would be a lot of work setting up the environment and maintaining it).我想模拟数据库,更具体地说是db_conn object,因为我不想使用真正的数据库(设置环境和维护它需要做很多工作)。

Any suggestions on how I can emulate the db_conn ?关于如何模拟db_conn的任何建议?

I've been exploring pytest-mock and magicmock but I don't think or know how to mock the db_conn in my test.我一直在探索pytest-mockmagicmock ,但我不知道也不知道如何在我的测试中模拟db_conn

I believe you are right not testing cases on a real database because it's not unit testing anymore if you are using external dependencies.我相信你没有在真实数据库上测试用例是对的,因为如果你使用外部依赖项,它就不再是单元测试了。

There is a possibility to specify return-value and customize it ( different return values on each iteration even ) for Mock or MagicMock objects.可以为MockMagicMock对象指定return-value并自定义它( 甚至每次迭代都有不同的返回值)。

from unittest.mock import Mock, patch 

from app import db_conn


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

Keep in mind that in each patch you should specify the import path of db_conn - the path where db_conn **is used (I assume it's a different path in each test), not where it is defined.请记住,在每个patch中,您应该指定db_conn的导入路径 -使用db_conn ** 的路径(我假设它在每个测试中都是不同的路径),而不是定义它的位置。

To answer the intial question "How to mock a imported object with pytest-mock or magicmock" you can do:要回答最初的问题“如何使用 pytest-mock 或 magicmock 模拟导入的 object”,您可以执行以下操作:

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(...)

note that you do not patch the actual source of db请注意,您没有修补db 的实际来源

For your other use case对于您的其他用例

I want to mock the database (using a mongo database), more specifically the "db_conn" object我想模拟数据库(使用 mongo 数据库),更具体地说是“db_conn”object

you similarly follow the hints of the link above:您同样遵循上面链接的提示:

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

Given that, you will notice in your tests that db from `db = db_conn.db_name2.db_collection2' will create another mock object.鉴于此,您会在测试中注意到来自 ` db = db_conn.db_name2.db_collection2' 的 db 将创建另一个模拟 object。 Calls to that object will be recorded as well.对 object 的调用也将被记录。 In such a way you will be able to trace history of calls and values assignments as well.通过这种方式,您还可以跟踪调用和值分配的历史记录。


Furthermore, see an example how to pach mongo db.此外,请参阅如何修补 mongo db 的示例。

For testing of Flask apps see the documentation of flask .有关 Flask 应用程序的测试,请参阅 flask的文档 Also this is a nice explanation as well, and uses DB connections . 这也是一个很好的解释,并使用 DB 连接

As a general hint, like @MikeMajara mentioned - separate your code more into smaller functions that are also easy to test.作为一般提示,就像@MikeMajara 提到的那样 - 将您的代码更多地分成更易于测试的更小的函数。 In tradition to TDD: write tests first, implement later, and refactor (especially DRY!) TDD 的传统:先编写测试,后实施,然后重构(尤其是 DRY!)

Separation of concerns.关注点分离。 Build methods that do one, and only one thing.构建只做一件事的方法。 Even more if you are going with TDD.如果您要使用 TDD,那就更是如此。 In your example some_func2 does more than one.在您的示例中, some_func2 不止一个。 You could refactor as follows:你可以重构如下:

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)

With this approach you could easily test get_object_from_db and check_condition_on_object separately.使用这种方法,您可以轻松地分别测试get_object_from_dbcheck_condition_on_object This will improve readability, avoid bugs, and help detecting these if they appear at some point.这将提高可读性,避免错误,并在它们出现时帮助检测它们。


About "mocking an imported object" .关于“模拟导入的对象” You might be trying to mock an object with a library that is meant for a more advance case than yours.您可能正在尝试使用一个用于比您的更高级案例的库来模拟 object。 These libraries provide you with a bunch of methods surrounding test environment out of the box that you might not need.这些库为您提供了一堆您可能不需要的开箱即用的测试环境方法。 From the looks, you just want to populate an object with mock data, and/or interact with a mocked db_connection instance.从外观上看,您只想用模拟数据填充 object,和/或与模拟的 db_connection 实例交互。 So...所以...

To populate , I would simplify: You know the condition you want to test and you want to check if the result for a given object is the expected one.要填充,我会简化:您知道要测试的条件,并且要检查给定 object 的结果是否是预期的。 Just build yourself a test_object_provider.py that returns your known cases for true|false .只需为自己构建一个test_object_provider.py ,它会返回您已知的true|false案例。 No need to make things more complex.没有必要让事情变得更复杂。

To use a fake MongoDB connection you can try with mongomock .要使用假的 MongoDB 连接,您可以尝试使用mongomock (although ideally you would test mongo with a real instance in a separate test). (尽管理想情况下您会在单独的测试中使用真实实例测试 mongo)。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM