如何在Python中进行基本依赖注入(用于模拟/测试目的)

Pre*_*zel 11 python unit-testing dependency-injection mocking

Python对我来说是一种相对较新的语言.单元测试和依赖注入是我现在已经做了一段时间的事情,所以我从C#的角度来熟悉它.

最近,我写了这段Python代码:

import requests  # my dependency: http://docs.python-requests.org/en/latest/

class someClass:
    def __init__(self):
        pass

    def __do(self, url, datagram):
        return requests.post(self, url, datagram)
Run Code Online (Sandbox Code Playgroud)

然后我意识到我刚刚创建了一个硬编码的依赖项.的Bleh.

我曾考虑改变我的代码来做"构造函数"依赖注入:

def __init__(self,requestLib=requests):
    self.__request = requestLib

def __do(self, url, datagram):
    return self.__request.post(self, url, datagram)
Run Code Online (Sandbox Code Playgroud)

这现在允许我为单元测试注入假/模拟依赖,但不确定这是否被认为是Python-ic.所以我向Python社区寻求指导.

有哪些Python-ic方法可以做基本的DI(主要是为了编写利用Mocks/Fakes的单元测试)?

ADDENDUM对于对模拟答案感到好奇的人,我决定在这里问一个单独的问题:@ mock.patch如何知道每个模拟对象使用哪个参数?

Mad*_*bat 9

不要那样做.只需正常导入请求并正常使用它们.将库作为参数传递给构造函数是一件有趣的事情,但对于您的目的而言,并不是非常pythonic和不必要的.要在单元测试中模拟事物,请使用模拟库.在python 3中,它内置于标准库中

https://docs.python.org/3.4/library/unittest.mock.html

在python 2中,您需要单独安装它

https://pypi.python.org/pypi/mock

你的测试代码看起来像这样(使用python 3版本)

from unittest import TestCase
from unittest.mock import patch

class MyTest(TestCase):
    @patch("mymodule.requests.post")
    def test_my_code(self, mock_post):
        # ... do my thing here...
Run Code Online (Sandbox Code Playgroud)

  • 所以我试图解析你的代码.'@ patch("mymodule.requests")有什么作用?为什么要将"请求"传递给"test_my_code"方法.另外,"requests.post"的模拟效果如何? (3认同)
  • 补丁装饰器将代码中的 requests 模块替换为模拟。我的代码很可能是不正确的,您必须分别修补请求中的每个函数才能使其正常工作。修补后的函数作为参数传递给测试,因此您可以对其进行断言。阅读单元测试和模拟的文档以获取有关其用法的更多信息。 (2认同)

Rod*_*ins 6

虽然注入 requests 模块可能有点太多,但将一些依赖项作为可注入性是一个很好的做法。

多年使用没有任何 DI 自动装配框架的 Python 和带有 Spring 的 Java 之后,我开始意识到简单的 Python 代码通常不需要使用自动装配的依赖注入框架(自动装配是 Guice 和 Spring 在 Java 中所做的),即,只是做这样的事情可能就足够了:

def foo(dep = None):  # great for unit testing!
    ...
Run Code Online (Sandbox Code Playgroud)

这是纯粹的依赖注入(非常简单),但没有为您自动注入它们的神奇框架。调用者必须实例化依赖项,或者您可以这样做:

def __init__(self, dep = None):
    self.dep = dep or Dep()
Run Code Online (Sandbox Code Playgroud)

但是,当您寻求更大的应用程序时,这种方法不会削减它。为此,我提出了一个可注入的微框架,它不会让人感觉非 Pythonic,但会提供一流的依赖注入自动装配。

人类依赖注入™的座右铭下,它是这样的:

# some_service.py
class SomeService:
    @autowired
    def __init__(
        self,
        database: Autowired(Database),
        message_brokers: Autowired(List[Broker]),
    ):
        pending = database.retrieve_pending_messages()
        for broker in message_brokers:
            broker.send_pending(pending)
Run Code Online (Sandbox Code Playgroud)
# database.py
@injectable
class Database:
    ...
Run Code Online (Sandbox Code Playgroud)
# message_broker.py
class MessageBroker(ABC):
    def send_pending(messages):
        ...
Run Code Online (Sandbox Code Playgroud)
# kafka_producer.py
@injectable
class KafkaProducer(MessageBroker):
    ...
Run Code Online (Sandbox Code Playgroud)
# sqs_producer.py
@injectable
class SQSProducer(MessageBroker):
    ...
Run Code Online (Sandbox Code Playgroud)

  • 如果这个答案包括如何在单元测试中使用所有这些,那就太好了 (3认同)