Google App Engine中的依赖注入(Python)
我想在我用Python写的Google App Engine应用中实现最大的可测试性。
简单来说,我正在创建一个通用的基础处理器,它继承自google.appengine.ext.webapp.RequestHandler
。我的基础处理器会提供一些在应用中常用的功能,比如数据存储的操作、会话对象等等。
当WSGIApplication
接收到请求时,它会找到与请求的URL对应的处理器类,然后调用这个类的构造函数,接着会调用一个叫initialize
的方法,并传入request
和response
对象。
为了方便测试,我希望能够“模拟”这些对象(还有我自己创建的对象)。所以我的问题是,如何将这些模拟对象注入进去?我可以在基础处理器中重写initialize
方法,检查一个全局的“测试标志”,然后初始化一些虚假的request
和response
对象。但在我看来,这样做似乎不太对。而且,我该如何初始化其他对象(这些对象可能依赖于request
和response
对象)呢?
如你所见,我对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 个回答
为什么不直接单独测试你的处理程序呢?也就是说,先创建一些假的请求和响应对象,然后实例化你想测试的处理程序,接着用这些假的对象调用 handler.initialize(request, response)。在这里其实不需要依赖注入。
实现你想要的最简单方法就是创建一个模块级的变量,这个变量用来存放你要创建的会话的类。
# 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,并且让你可以灵活地按照自己的方式进行测试。