【问题标题】:How do you mock the User service in App Engine?您如何模拟 App Engine 中的用户服务?
【发布时间】:2011-05-28 03:01:03
【问题描述】:

我正在使用 Google App Engine testbed 框架编写带有模拟对象的测试用例。这记录在here。我的数据存储测试使用模拟数据库 (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

意思是假用户服务告诉我的应用当前用户没有登录。我怎样才能告诉假用户服务假装用户已登录?理想情况下,我希望能够指定假用户的昵称、电子邮件、user_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()

【问题讨论】:

    标签: python google-app-engine


    【解决方案1】:

    好吧,我认为没有官方的方法可以做到这一点,但我一直在阅读源代码,并且发现了一种到目前为止运行良好的“hack”方法。 (通常我会担心使用未记录的行为,但它是一个测试套件,所以它只有在开发服务器上工作时才重要。)

    开发服务器根据三个环境变量找出当前登录的用户:

    • USER_EMAIL:用户的电子邮件地址,用户的昵称。
    • USER_ID:用户的唯一 Google ID(字符串)。
    • USER_IS_ADMIN:如果用户是非管理员,则为“0”,如果用户是管理员,则为“1”。

    您可以像设置任何其他环境变量一样使用os.environ 来设置它们,它们会立即生效(显然这在生产服务器上不起作用)。但是您可以将它们与 testbed 的 user_stub 一起使用,并且当您停用 testbed 时它们将被重置(您应该在 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)
    

    【讨论】:

    • 这基本上是正确的想法。除了你想直接使用testbed.setup_env() 而不是os.environ
    • 我对此表示反对,因为这会污染环境。应该注意的是,需要进行清理。正如@ryan 所说,这可以使用 testbed 来完成。
    【解决方案2】:

    以下是我模拟登录用户的方法:

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

    【讨论】:

    • 行得通,但似乎testbed.setup_env()必须在testbed.activate()之前,否则没有效果。
    • @gargc 很抱歉,您所说的内容是错误的并且可能是“危险的”。请参阅我的答案以进行演示。
    • @siebz0r 好电话!顺便说一句,我刚刚发现为什么我必须更改调用顺序才能使其正常工作。 USER_EMAIL 和 USER_ID 作为默认环境的一部分在激活时设置为空字符串(user_is_admin 不会发生这种情况)。解决方案是将overwrite=True 传递给setup_env,否则环境中已经存在的任何变量都是ignored,导致get_current_user() 始终为None。
    • 这对我来说需要overwrite 参数,如this answer 中所述。
    【解决方案3】:

    除了Bijan的回答:

    google.appengine.api.users 的实际签到如下所示:

    def is_current_user_admin():
        return (os.environ.get('USER_IS_ADMIN', '0')) == '1'
    

    因此,关键是将环境变量USER_IS_ADMIN 设置为'1'。这可以通过多种方式完成,但请注意您正在修改全局变量,因此这可能会影响其他代码。关键是进行适当的清理。

    可以使用Mock librarypatch os.environ,使用Testbed 或滚动他们自己的创意方式。我更喜欢使用Testbed,因为它暗示黑客与 appengine 相关。 3.3 之前的 Python 版本中不包含 Mock,因此这会增加额外的测试依赖项。

    额外说明:当使用unittest module 时,我更喜欢使用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
    

    【讨论】:

      【解决方案4】:

      这是我根据此处的答案为我的测试创建的几个辅助函数。我将它们放在test_helper 模块中:

      # tests/test_helper.py
      import hashlib
      
      def mock_user(testbed, user_email='test@example.com', is_admin=False):
          user_id = hashlib.md5(user_email).hexdigest()
          is_admin = str(int(is_admin))
      
          testbed.setup_env(USER_EMAIL=user_email,
                            USER_ID=user_id,
                            USER_IS_ADMIN=is_admin,
                            overwrite=True)
          testbed.init_user_stub()
      
      def mock_admin_user(testbed, user_email='admin@example.com'):
          mock_user(testbed, user_email, True)
      

      使用示例(NoseGAE):

      import unittest
      
      from google.appengine.ext import ndb, testbed
      from google.appengine.api import users
      
      from tests.test_helper import mock_user, mock_admin_user
      
      class MockUserTest(unittest.TestCase):
          def setUp(self):
              self.testbed = testbed.Testbed()
              self.testbed.activate()
              self.testbed.init_datastore_v3_stub()
              self.testbed.init_memcache_stub()
              ndb.get_context().clear_cache()
      
          def tearDown(self):
              self.testbed.deactivate()
      
          def test_should_mock_user_login(self):
              self.assertIsNone(users.get_current_user())
              self.assertFalse(users.is_current_user_admin())
      
              mock_user(self.testbed)
              user = users.get_current_user()
              self.assertEqual(user.email(), 'test@example.com')
              self.assertFalse(users.is_current_user_admin())
      
              mock_admin_user(self.testbed)
              admin = users.get_current_user()
              self.assertEqual(admin.email(), 'admin@example.com')
              self.assertTrue(users.is_current_user_admin())
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-09-13
        • 2018-08-13
        • 2012-09-02
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-07-17
        相关资源
        最近更新 更多