如何在 Python 中进行基本依赖注入(用于 mocking/testing 目的)
How to do basic dependency injection in Python (for mocking/testing purposes)
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)
然后我意识到我刚刚创建了一个硬编码的依赖项。呸。
我曾考虑更改我的代码以执行 "Constructor" 依赖注入:
def __init__(self,requestLib=requests):
self.__request = requestLib
def __do(self, url, datagram):
return self.__request.post(self, url, datagram)
这现在允许我为了单元测试注入 fake/mock 依赖项,但不确定这是否被认为是 Python-ic。因此,我向 Python 社区寻求指导。
有哪些 Python-ic 方法来执行基本 DI(主要是为了编写利用 Mocks/Fakes 的单元测试)的一些示例?
ADDENDUM 对于任何对模拟答案感到好奇的人,我决定在这里问一个单独的问题:
不要那样做。只需像往常一样导入请求并像往常一样使用它们。将库作为参数传递给您的构造函数是一件有趣的事情,但不是很 pythonic 并且对于您的目的来说是不必要的。要在单元测试中模拟事物,请使用模拟库。在python 3 中内置于标准库
https://docs.python.org/3.4/library/unittest.mock.html
而在python2中需要单独安装
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...
虽然注入请求模块可能有点太多,但将一些依赖项作为可注入的是一个非常好的做法。
在使用没有任何 DI 自动装配框架的 Python 和使用 Spring 的 Java 多年后,我开始意识到通常不需要简单的 Python 代码一个带有自动装配的依赖注入框架(自动装配是 Guice 和 Spring 都在 Java 中做的),即,只做这样的事情可能就足够了:
def foo(dep = None): # great for unit testing!
...
这是纯粹的依赖注入(非常简单),但没有为您自动注入它们的神奇框架。调用者必须实例化依赖关系,或者你可以这样做:
def __init__(self, dep = None):
self.dep = dep or Dep()
当您寻求更大的应用程序时,这种方法不会削减它。为此,我提出了 injectable 一个微型框架,它不会让人感觉非 pythonic,但会提供第一个 class 依赖注入自动装配。
根据座右铭 人类依赖注入™ 这就是它的样子:
# 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)
# database.py
@injectable
class Database:
...
# message_broker.py
class MessageBroker(ABC):
def send_pending(messages):
...
# kafka_producer.py
@injectable
class KafkaProducer(MessageBroker):
...
# sqs_producer.py
@injectable
class SQSProducer(MessageBroker):
...
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)
然后我意识到我刚刚创建了一个硬编码的依赖项。呸。
我曾考虑更改我的代码以执行 "Constructor" 依赖注入:
def __init__(self,requestLib=requests):
self.__request = requestLib
def __do(self, url, datagram):
return self.__request.post(self, url, datagram)
这现在允许我为了单元测试注入 fake/mock 依赖项,但不确定这是否被认为是 Python-ic。因此,我向 Python 社区寻求指导。
有哪些 Python-ic 方法来执行基本 DI(主要是为了编写利用 Mocks/Fakes 的单元测试)的一些示例?
ADDENDUM 对于任何对模拟答案感到好奇的人,我决定在这里问一个单独的问题:
不要那样做。只需像往常一样导入请求并像往常一样使用它们。将库作为参数传递给您的构造函数是一件有趣的事情,但不是很 pythonic 并且对于您的目的来说是不必要的。要在单元测试中模拟事物,请使用模拟库。在python 3 中内置于标准库
https://docs.python.org/3.4/library/unittest.mock.html
而在python2中需要单独安装
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...
虽然注入请求模块可能有点太多,但将一些依赖项作为可注入的是一个非常好的做法。
在使用没有任何 DI 自动装配框架的 Python 和使用 Spring 的 Java 多年后,我开始意识到通常不需要简单的 Python 代码一个带有自动装配的依赖注入框架(自动装配是 Guice 和 Spring 都在 Java 中做的),即,只做这样的事情可能就足够了:
def foo(dep = None): # great for unit testing!
...
这是纯粹的依赖注入(非常简单),但没有为您自动注入它们的神奇框架。调用者必须实例化依赖关系,或者你可以这样做:
def __init__(self, dep = None):
self.dep = dep or Dep()
当您寻求更大的应用程序时,这种方法不会削减它。为此,我提出了 injectable 一个微型框架,它不会让人感觉非 pythonic,但会提供第一个 class 依赖注入自动装配。
根据座右铭 人类依赖注入™ 这就是它的样子:
# 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)
# database.py
@injectable
class Database:
...
# message_broker.py
class MessageBroker(ABC):
def send_pending(messages):
...
# kafka_producer.py
@injectable
class KafkaProducer(MessageBroker):
...
# sqs_producer.py
@injectable
class SQSProducer(MessageBroker):
...