如何在App Engine中模拟用户服务?

22 投票
4 回答
3567 浏览
提问于 2025-04-16 18:30

我正在使用 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 个回答

10

除了 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
11

这是我用来模拟一个已登录用户的方法:

self.testbed.setup_env(USER_EMAIL='usermail@gmail.com',USER_ID='1', USER_IS_ADMIN='0')
self.testbed.init_user_stub()
17

我觉得没有官方的办法来做到这一点,不过我在看源代码的时候发现了一种“黑科技”的方法,目前效果不错。(通常我会担心使用一些没有文档的功能,但因为这是一个测试环境,所以只要在开发服务器上能用就行。)

开发服务器通过三个环境变量来判断当前登录的用户:

  • 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)

撰写回答