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

时间:2015-10-26 15:03:06

标签: 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)

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

我曾考虑过将代码更改为"构造函数"依赖注入:

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

def __do(self, url, datagram):
    return self.__request.post(self, url, datagram)

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

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

ADDENDUM 对于对模拟答案感到好奇的人,我决定在此处单独提问:How does @mock.patch know which parameter to use for each mock object?

2 个答案:

答案 0 :(得分:8)

不要那样做。只需正常导入请求并正常使用它们。将库作为参数传递给构造函数是一件有趣的事情,但对于您的目的而言,并不是非常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...

答案 1 :(得分:0)

虽然注入请求模块可能有点过多,但将一些依赖项作为可注入方式是一种非常好的做法。

可能会出现一个完整的框架正是您所需要的。为此,有一些很好的模块,例如Injector

更简约和直接的方法是使用装饰师为你完成这项工作。 out there有一些模块。

我维护了一个这样的模块:Injectable,它提供了一个Python 3 @autowired装饰器,以实现简单,干净的依赖注入。

这个装饰者的要点是:

  • 该功能无需了解所有
  • 的自动装配
  • 依赖关系可以延迟初始化
  • 如果需要,调用者可以显式传递依赖项实例

基本上你转这样的代码

def __init__(self, *, model: Model = None, service: Service = None):
    if model is None:
        model = Model()

    if service is None:
        service = Service()

    self.model = model
    self.service = service
    # actual code

进入

@autowired
def __init__(self, *, model: Model, service: Service):
    self.model = model
    self.service = service
    # actual code

没有复杂的东西,没有设置,没有强制执行工作流程。