Google App Engine中的依赖注入(Python)

3 投票
2 回答
888 浏览
提问于 2025-04-16 05:35

我想在我用Python写的Google App Engine应用中实现最大的可测试性。

简单来说,我正在创建一个通用的基础处理器,它继承自google.appengine.ext.webapp.RequestHandler。我的基础处理器会提供一些在应用中常用的功能,比如数据存储的操作、会话对象等等。

WSGIApplication接收到请求时,它会找到与请求的URL对应的处理器类,然后调用这个类的构造函数,接着会调用一个叫initialize的方法,并传入requestresponse对象。

为了方便测试,我希望能够“模拟”这些对象(还有我自己创建的对象)。所以我的问题是,如何将这些模拟对象注入进去?我可以在基础处理器中重写initialize方法,检查一个全局的“测试标志”,然后初始化一些虚假的requestresponse对象。但在我看来,这样做似乎不太对。而且,我该如何初始化其他对象(这些对象可能依赖于requestresponse对象)呢?

如你所见,我对Python还不太熟悉,所以任何建议都非常欢迎。

编辑:

有人指出,没有一些代码的话,这个问题有点难回答,所以我来提供一些代码:

from google.appengine.ext import webapp
from ..utils import gmemsess
from .. import errors
_user_id_name = 'userid'

class Handler(webapp.RequestHandler):
    '''
    classdocs
    '''

    def __init__(self):
        '''
        Constructor
        '''
        self.charset = 'utf8'
        self._session = None

    def _getsession(self):
        if not self._session:
            self._session = gmemsess.Session(self)
        return self._session

    def _get_is_logged_in(self):
        return self.session.has_key(_user_id_name)

    def _get_user_id(self):
        if not self.is_logged_in:
            raise errors.UserNotLoggedInError()
        return self.session[_user_id_name]

    session = property(_getsession)
    is_logged_in = property(_get_is_logged_in)
    user_id = property(_get_user_id)

如你所见,这里根本没有进行依赖注入。会话对象是通过调用gmemsess.Session(self)创建的。Session类需要一个有request对象的类(它用这个来读取cookie的值)。在这种情况下,self确实有这样的属性,因为它继承自webapp.RequestHandler。而且它之所以有这个对象,是因为在调用(空的)构造函数后,WSGIApplication会调用一个叫initialize的方法,这个方法会设置这个对象(还有响应对象)。initialize方法是在基类(webapp.RequestHandler)中声明的。它看起来是这样的:

def initialize(self, request, response):
    """Initializes this request handler with the given Request and 
     Response."""
    self.request = request
    self.response = response

当请求被发出时,WSGIApplication类会执行以下操作:

def __call__(self, environ, start_response):
    """Called by WSGI when a request comes in."""
    request = self.REQUEST_CLASS(environ)
    response = self.RESPONSE_CLASS()
    WSGIApplication.active_instance = self
    handler = None
    groups = ()
    for regexp, handler_class in self._url_mapping:
        match = regexp.match(request.path)
        if match:
            handler = handler_class()
            handler.initialize(request, response)
            groups = match.groups()
            break

    self.current_request_args = groups
    if handler:
        try:
            method = environ['REQUEST_METHOD']
            if method == 'GET':
                handler.get(*groups)
            elif method == 'POST':
                handler.post(*groups)
            '''SNIP'''

我们关注的行是:

     handler = handler_class()
     handler.initialize(request, response)

如你所见,它在我的处理器类上调用了空的构造函数。这对我来说是个问题,因为我想要在运行时注入我的会话对象的类型,这样我的类看起来应该是这样的(显示片段):

    def __init__(self, session_type):
        '''
        Constructor
        '''
        self.charset = 'utf8'
        self._session = None
        self._session_type = session_type

    def _getsession(self):
        if not self._session:
            self._session = self._session_type(self)
        return self._session

然而,我无法理解如何实现这一点,因为WSGIApplication只调用空的构造函数。我想我可以在某个全局变量中注册session_type,但这并不符合我理解的依赖注入的理念。不过,正如我所说,我对Python还不太熟悉,也许我只是想错了。无论如何,我更希望传入一个会话对象,而不是它的类型,但在这里看起来有点不可能。

任何建议都非常感谢。

2 个回答

1

为什么不直接单独测试你的处理程序呢?也就是说,先创建一些假的请求和响应对象,然后实例化你想测试的处理程序,接着用这些假的对象调用 handler.initialize(request, response)。在这里其实不需要依赖注入。

2

实现你想要的最简单方法就是创建一个模块级的变量,这个变量用来存放你要创建的会话的类。

# myhandler.py
session_class = gmemsess.Session

class Handler(webapp.Request
    def _getsession(self):
        if not self._session:
            self._session = session_class(self)
        return self._session

然后,无论你在哪里决定是进行测试还是运行程序:

import myhandler

if testing:
    myhandler.session_class = MyTestingSession

这样做几乎不需要改动你的处理类,也不需要改动WSGIApplication,并且让你可以灵活地按照自己的方式进行测试。

撰写回答