Python 单元测试中的依赖注入
Dependency Injection in Unit Test with Python
我在学习python
我想知道是否有一种机制可以 "inject" 将一个对象(在我的例子中是一个假对象)添加到 class 被测对象中,而无需在 costructor/setter 中显式添加它.
## source file
class MyBusinessClass():
def __init__(self):
self.__engine = RepperEngine()
def doSomething(self):
## bla bla ...
success
## test file
## fake I'd like to inkject
class MyBusinessClassFake():
def __init__(self):
pass
def myPrint(self) :
print ("Hello from Mock !!!!")
class Test(unittest.TestCase):
## is there an automatic mechanism to inject MyBusinessClassFake
## into MyBusinessClass without costructor/setter?
def test_XXXXX_whenYYYYYY(self):
unit = MyBusinessClass()
unit.doSomething()
self.assertTrue(.....)
在我的测试中,我想 "inject" 对象 "engine" 而不是在构造函数中传递它。我尝试了几个选项(例如:@patch ...)但没有成功。
您的问题似乎有些不清楚,但是没有什么可以阻止您使用 class 继承来覆盖原始方法。在这种情况下,导数 class 将如下所示:
class MyBusinessClassFake(MyBusinessClass):
def __init__(self):
pass
Python 不需要 IOC。这是一个 Pythonic 方法。
class MyBusinessClass(object):
def __init__(self, engine=None):
self._engine = engine or RepperEngine()
# Note: _engine doesn't exist until constructor is called.
def doSomething(self):
## bla bla ...
success
class Test(unittest.TestCase):
def test_XXXXX_whenYYYYYY(self):
mock_engine = mock.create_autospec(RepperEngine)
unit = MyBusinessClass(mock_engine)
unit.doSomething()
self.assertTrue(.....)
你也可以去掉 class 来绕过构造器
class MyBusinessClassFake(MyBusinessClass):
def __init__(self): # we bypass super's init here
self._engine = None
然后在您的设置中
def setUp(self):
self.helper = MyBusinessClassFake()
现在在您的测试中您可以使用上下文管理器。
def test_XXXXX_whenYYYYYY(self):
with mock.patch.object(self.helper, '_engine', autospec=True) as mock_eng:
...
如果你想在不使用构造函数的情况下注入它,那么你可以将它添加为 class 属性。
class MyBusinessClass():
_engine = None
def __init__(self):
self._engine = RepperEngine()
现在绕过 __init__
:
class MyBusinessClassFake(MyBusinessClass):
def __init__(self):
pass
现在您可以简单地赋值:
unit = MyBusinessClassFake()
unit._engine = mock.create_autospec(RepperEngine)
在使用没有任何 DI 自动装配框架的 Python 和使用 Spring 的 Java 多年后,我开始意识到简单的 Python 代码通常不需要框架对于没有自动装配的依赖注入(自动装配是 Guice 和 Spring 都在 Java 中做的),即,只做这样的事情就足够了:
def foo(dep = None): # great for unit testing!
self.dep = dep or Dep() # callers can not care about this too
...
这是纯粹的依赖注入(非常简单),但没有神奇的框架为您自动注入它们(即自动装配),也没有控制反转。
与@Dan 不同,我不同意 Python 不需要 IoC:控制反转是一个简单的概念,即框架剥夺了对某些事物的控制,通常是为了提供抽象并去除样板代码。当您使用模板 classes 时,这就是 IoC。 IoC 的好坏完全取决于框架如何实现它。
也就是说,依赖注入是一个不需要 IoC 的简单概念。自动装配 DI 可以。
当我处理更大的应用程序时,简单化的方法不再有效:样板代码太多,DI 的关键优势缺失:一次更改某物的实现并在所有应用程序中反映出来classes 取决于它。如果您的应用程序的许多部分都关心如何初始化某个依赖项,并且您更改了此初始化或想要更改 classes,则您将不得不逐个更改它。使用 DI 框架会更容易。
所以我想出了 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
我想知道是否有一种机制可以 "inject" 将一个对象(在我的例子中是一个假对象)添加到 class 被测对象中,而无需在 costructor/setter 中显式添加它.
## source file
class MyBusinessClass():
def __init__(self):
self.__engine = RepperEngine()
def doSomething(self):
## bla bla ...
success
## test file
## fake I'd like to inkject
class MyBusinessClassFake():
def __init__(self):
pass
def myPrint(self) :
print ("Hello from Mock !!!!")
class Test(unittest.TestCase):
## is there an automatic mechanism to inject MyBusinessClassFake
## into MyBusinessClass without costructor/setter?
def test_XXXXX_whenYYYYYY(self):
unit = MyBusinessClass()
unit.doSomething()
self.assertTrue(.....)
在我的测试中,我想 "inject" 对象 "engine" 而不是在构造函数中传递它。我尝试了几个选项(例如:@patch ...)但没有成功。
您的问题似乎有些不清楚,但是没有什么可以阻止您使用 class 继承来覆盖原始方法。在这种情况下,导数 class 将如下所示:
class MyBusinessClassFake(MyBusinessClass):
def __init__(self):
pass
Python 不需要 IOC。这是一个 Pythonic 方法。
class MyBusinessClass(object):
def __init__(self, engine=None):
self._engine = engine or RepperEngine()
# Note: _engine doesn't exist until constructor is called.
def doSomething(self):
## bla bla ...
success
class Test(unittest.TestCase):
def test_XXXXX_whenYYYYYY(self):
mock_engine = mock.create_autospec(RepperEngine)
unit = MyBusinessClass(mock_engine)
unit.doSomething()
self.assertTrue(.....)
你也可以去掉 class 来绕过构造器
class MyBusinessClassFake(MyBusinessClass):
def __init__(self): # we bypass super's init here
self._engine = None
然后在您的设置中
def setUp(self):
self.helper = MyBusinessClassFake()
现在在您的测试中您可以使用上下文管理器。
def test_XXXXX_whenYYYYYY(self):
with mock.patch.object(self.helper, '_engine', autospec=True) as mock_eng:
...
如果你想在不使用构造函数的情况下注入它,那么你可以将它添加为 class 属性。
class MyBusinessClass():
_engine = None
def __init__(self):
self._engine = RepperEngine()
现在绕过 __init__
:
class MyBusinessClassFake(MyBusinessClass):
def __init__(self):
pass
现在您可以简单地赋值:
unit = MyBusinessClassFake()
unit._engine = mock.create_autospec(RepperEngine)
在使用没有任何 DI 自动装配框架的 Python 和使用 Spring 的 Java 多年后,我开始意识到简单的 Python 代码通常不需要框架对于没有自动装配的依赖注入(自动装配是 Guice 和 Spring 都在 Java 中做的),即,只做这样的事情就足够了:
def foo(dep = None): # great for unit testing!
self.dep = dep or Dep() # callers can not care about this too
...
这是纯粹的依赖注入(非常简单),但没有神奇的框架为您自动注入它们(即自动装配),也没有控制反转。
与@Dan 不同,我不同意 Python 不需要 IoC:控制反转是一个简单的概念,即框架剥夺了对某些事物的控制,通常是为了提供抽象并去除样板代码。当您使用模板 classes 时,这就是 IoC。 IoC 的好坏完全取决于框架如何实现它。
也就是说,依赖注入是一个不需要 IoC 的简单概念。自动装配 DI 可以。
当我处理更大的应用程序时,简单化的方法不再有效:样板代码太多,DI 的关键优势缺失:一次更改某物的实现并在所有应用程序中反映出来classes 取决于它。如果您的应用程序的许多部分都关心如何初始化某个依赖项,并且您更改了此初始化或想要更改 classes,则您将不得不逐个更改它。使用 DI 框架会更容易。
所以我想出了 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):
...