如何在App Engine中模拟用户服务?
我正在使用 Google App Engine 的 testbed
框架来编写测试案例,里面用到了模拟对象。这部分的文档可以在这里找到。我已经成功地让我的数据存储测试在模拟数据库(Testbed.init_datastore_v3_stub
)上运行,这样我的测试案例就可以在一个快速且全新的数据库上运行,每个测试案例都会重新初始化数据库。现在我想测试一些依赖于当前用户的功能。
还有一个叫做 Testbed.init_user_stub
的测试服务,我可以激活它来获取“假”的用户服务。不幸的是,这个服务似乎没有任何文档。我是这样激活和使用它的:
import unittest
from google.appengine.ext import testbed
from google.appengine.api import users
class MyTest(unittest.TestCase):
def setUp(self):
self.testbed = testbed.Testbed()
self.testbed.activate()
self.testbed.init_user_stub()
def testUser(self):
u = users.get_current_user()
self.assertNotEqual(u, None)
问题是,我还没有找到任何方法来告诉这个“假”的用户服务去认证一个“假”的用户。所以运行这个测试时,我(可以预见地)得到了
AssertionError: None == None
这意味着假用户服务告诉我的应用当前用户没有登录。我该如何告诉假用户服务假装有一个用户已经登录呢?理想情况下,我希望能够指定假用户的昵称、电子邮件、用户 ID,以及他们是否是管理员。看起来这应该是一个很常见的需求(因为你需要测试应用在 a) 没有人登录,b) 有用户登录,以及 c) 管理员登录时的表现),但是在网上搜索“init_user_stub”几乎没有结果。
注意:如果你想测试上面的程序,需要在顶部添加这个:
import sys
sys.path.append('/PATH/TO/APPENGINE/SDK')
import dev_appserver
dev_appserver.fix_sys_path()
并在底部添加这个:
if __name__ == '__main__':
unittest.main()
4 个回答
除了 Bijan 的回答之外:
在 google.appengine.api.users
中,实际的检查是这样的:
def is_current_user_admin():
return (os.environ.get('USER_IS_ADMIN', '0')) == '1'
关键是要把环境变量 USER_IS_ADMIN
设置为 '1'
。这可以通过多种方式来实现,但要注意,你是在修改一个全局变量,这可能会影响到其他代码。重要的是要做好清理工作。
你可以使用 Mock 库 来 修补 os.environ
,也可以使用 Testbed
,或者自己想办法。我更喜欢使用 Testbed
,因为它暗示这个操作和 appengine 有关。Mock 在 Python 3.3 之前的版本中没有包含,所以这会增加额外的测试依赖。
额外说明:在使用 unittest
模块 时,我更喜欢使用 addCleanup
,而不是 tearDown
,因为清理工作在 setUp
失败时也会被调用。
示例测试:
import unittest
from google.appengine.api import users
from google.appengine.ext import testbed
class AdminTest(unittest.TestCase):
def setUp(self):
tb = testbed.Testbed()
tb.activate()
# ``setup_env`` takes care of the casing ;-)
tb.setup_env(user_is_admin='1')
self.addCleanup(tb.deactivate)
def test_is_current_user_admin(self):
self.assertTrue(users.is_current_user_admin())
注意: Testbed.setup_env
应该在 Testbed.activate
之后调用。Testbed
在激活时会对 os.environ
进行快照,这个快照在停用时会被恢复。如果在激活之前调用 Testbed.setup_env
,那么真实的 os.environ
会被修改,而不是临时实例,这样就会污染环境。
这应该按预期工作:
>>> import os
>>> from google.appengine.ext import testbed
>>>
>>> tb = testbed.Testbed()
>>> tb.activate()
>>> tb.setup_env(user_is_admin='1')
>>> assert 'USER_IS_ADMIN' in os.environ
>>> tb.deactivate()
>>> assert 'USER_IS_ADMIN' not in os.environ
>>>
这会污染环境:
>>> import os
>>> from google.appengine.ext import testbed
>>>
>>> tb = testbed.Testbed()
>>> tb.setup_env(user_is_admin='1')
>>> tb.activate()
>>> assert 'USER_IS_ADMIN' in os.environ
>>> tb.deactivate()
>>> assert 'USER_IS_ADMIN' not in os.environ
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
这是我用来模拟一个已登录用户的方法:
self.testbed.setup_env(USER_EMAIL='usermail@gmail.com',USER_ID='1', USER_IS_ADMIN='0')
self.testbed.init_user_stub()
我觉得没有官方的办法来做到这一点,不过我在看源代码的时候发现了一种“黑科技”的方法,目前效果不错。(通常我会担心使用一些没有文档的功能,但因为这是一个测试环境,所以只要在开发服务器上能用就行。)
开发服务器通过三个环境变量来判断当前登录的用户:
- USER_EMAIL:用户的电子邮件地址,还有用户的昵称。
- USER_ID:用户独特的谷歌ID(字符串形式)。
- USER_IS_ADMIN:如果用户不是管理员,这里是“0”;如果是管理员,这里是“1”。
你可以用 os.environ
来设置这些环境变量,就像设置其他环境变量一样,它们会立即生效(当然,这在生产服务器上是行不通的)。不过,你可以在测试环境的 user_stub 中使用它们,当你停用测试环境时,它们会被重置(你应该在 tearDown
中这样做,这样每个测试用例都能有一个干净的环境)。
因为设置环境变量有点麻烦,我写了一些包装函数来简化这个过程:
import os
def setCurrentUser(email, user_id, is_admin=False):
os.environ['USER_EMAIL'] = email or ''
os.environ['USER_ID'] = user_id or ''
os.environ['USER_IS_ADMIN'] = '1' if is_admin else '0'
def logoutCurrentUser():
setCurrentUser(None, None)