Как вы издеваетесь над службой пользователей в App Engine?

Я использую Google App Engine testbed рамки для написания тестовых случаев с фиктивными объектами. Это задокументировано здесь. Мои тесты хранилища данных работают хорошо с использованием базы данных mock (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 и указать, являются ли они администратором. Кажется, что это было бы довольно распространенной вещью (так как вам нужно проверить, как приложение ведет себя, когда а) никто не вошел в систему, б) пользователь вошел в систему, и в) администратор вошел в систему), но поиск в Google "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 ответа

Решение

Ну, я не думаю, что есть официальный способ сделать это, но я читал исходный код и нашел "хакерский" способ сделать это, который до сих пор работает хорошо. (Обычно я беспокоюсь об использовании недокументированного поведения, но это набор тестов, поэтому он имеет значение, только если он работает на сервере dev.)

Сервер dev определяет текущего пользователя, вошедшего в систему, на основе трех переменных среды:

  • USER_EMAIL: адрес электронной почты пользователя и псевдоним пользователя.
  • USER_ID: уникальный идентификатор пользователя Google (строка).
  • 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)

Вот что сработало для меня, чтобы имитировать вошедшего в систему пользователя:

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

В дополнение к ответу 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

Вот пара вспомогательных функций, которые я создал для своих тестов на основе ответов здесь. Я воткнул их в 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())
Другие вопросы по тегам