Подтвердить что ты не робот

Как вы издеваетесь над сервисом пользователя в 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

означает, что поддельная пользовательская служба сообщает моему приложению, что текущий пользователь не вошел в систему. Как я могу сказать, что поддельная пользовательская служба делает вид, что пользователь вошел в систему? В идеале я хотел бы указать псевдоним пользователя, email, user_id и независимо от того, является ли он администратором. Похоже, это было бы довольно обычным делом (поскольку вам нужно проверить, как приложение ведет себя, когда a) никто не вошел в систему, b) пользователь вошел в систему и c) администратор зарегистрировался), но googling "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()
4b9b3361

Ответ 1

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

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

  • USER_EMAIL: адрес электронной почты пользователя и псевдоним пользователя.
  • USER_ID: уникальный идентификатор пользователя (строка) пользователя.
  • USER_IS_ADMIN: "0", если пользователь не является администратором, "1", если пользователь является администратором.

Вы можете использовать os.environ, чтобы установить их так же, как и любую другую переменную среды, и они сразу же придут в действие (очевидно, это не будет работать на производственном сервере). Но вы можете использовать их с testbed user_stub, и они будут reset при деактивации тестового стенда (что вы должны сделать на 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)

Ответ 2

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

self.testbed.setup_env(USER_EMAIL='[email protected]',USER_ID='1', USER_IS_ADMIN='0')
self.testbed.init_user_stub()

Ответ 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 library для patch os.environ, используйте Testbed или сканируйте свой собственный творческий способ. Я предпочитаю использовать Testbed, поскольку он намекает, что взлом связан с appengine. Mock не включен в версии Python до 3.3, поэтому это добавляет дополнительную тестовую зависимость.

Дополнительная заметка: при использовании 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='[email protected]', 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='[email protected]'):
    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(), '[email protected]')
        self.assertFalse(users.is_current_user_admin())

        mock_admin_user(self.testbed)
        admin = users.get_current_user()
        self.assertEqual(admin.email(), '[email protected]')
        self.assertTrue(users.is_current_user_admin())