2011-05-28 10 views
20

Estoy usando el marco de Google App Engine testbed para escribir casos de prueba con objetos simulados. Esto está documentado here. Tengo mis pruebas de almacén de datos funcionando bien utilizando la base de datos simulada (Testbed.init_datastore_v3_stub), y esto permite que mis casos de prueba se ejecuten en una base de datos nueva y rápida que se reinicializa para cada caso de prueba. Ahora quiero probar la funcionalidad que depende del usuario actual.¿Cómo se burla del servicio del usuario en App Engine?

Hay otro servicio de prueba llamado Testbed.init_user_stub, que puedo activar para obtener el servicio de usuario "falso". Lamentablemente, no parece haber documentación para este. Estoy activación y el uso que de esta manera:

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) 

El problema es que no he encontrado ninguna manera de decirle al servicio de usuario "falso" para autenticar a un usuario "falso". Entonces, ejecutar esa prueba, I (previsiblemente) consigo

AssertionError: None == None 

que significa que el servicio de usuario falso está diciendo mi aplicación que el usuario actual no está en el sistema. ¿Cómo puedo saber el servicio de usuario falso pretender que un usuario se registra ¿en? Idealmente, me gustaría poder especificar el apodo falso del usuario, el correo electrónico, el ID de usuario y si son o no administradores. Parece que esto sería algo bastante común de querer (ya que es necesario probar cómo se comporta la aplicación cuando a) nadie está conectado, b) un usuario está conectado, yc) un administrador ha iniciado sesión, pero googleando "init_user_stub" devuelve casi nada.

Nota: Si desea probar el programa anterior, es necesario añadir esto a la parte superior:

import sys 
sys.path.append('/PATH/TO/APPENGINE/SDK') 
import dev_appserver 
dev_appserver.fix_sys_path() 

y esto hasta el fondo:

if __name__ == '__main__': 
    unittest.main() 

Respuesta

17

Bueno, yo no creo hay una forma oficial de hacerlo, pero he estado leyendo el código fuente y encontré una forma de "hackear" para hacerlo que funciona bien hasta ahora. (Normalmente me preocupa usar un comportamiento no documentado, pero es un conjunto de pruebas, así que solo importa si funciona en el servidor de desarrollo.)

El servidor de desarrollo descubre el usuario actualmente conectado en función de tres variables de entorno :

  • USER_EMAIL: dirección de correo electrónico del usuario, y el apodo del usuario.
  • USER_ID: ID de Google único del usuario (cadena).
  • USER_IS_ADMIN: "0" si el usuario no es administrador, "1" si el usuario es un administrador.

Puede utilizar os.environ para establecer estos como lo haría con cualquier otra variable de entorno, y que tenga efecto inmediato (obviamente, esto no va a funcionar en el servidor de producción). Pero puede usarlos con user_stub de testbed y se restablecerán cuando desactive el banco de pruebas (lo que debe hacer en tearDown, para que tenga un entorno nuevo para cada caso de prueba).

Desde el establecimiento de las variables de entorno es un poco difícil de manejar, escribí algunas funciones de contenedor para empaquetar para arriba:

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) 
+11

esto es básicamente la idea correcta. excepto que desea usar ['testbed.setup_env()'] (http://code.google.com/appengine/docs/python/tools/localunittesting.html#Changing_the_Default_Environment_Variables) en lugar de 'os.environ' directamente. – ryan

+1

Estoy bajando esto porque ** esto está contaminando el medio ambiente **. Cabe señalar que se requiere una limpieza. Esto se puede hacer usando testbed como @ryan declaró. – siebz0r

11

Aquí es lo que funcionó para mí para simular un usuario conectado:

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

que funciona, pero parece que testbed.setup_env() tiene que ir antes de testbed.activate(), de lo contrario no tiene ningún efecto. – gargc

+0

@gargc Lamento decir que lo que ha declarado es incorrecto y potencialmente "peligroso". Por favor, mira mi respuesta para una demostración. – siebz0r

+1

@ siebz0r ¡Buena llamada! Por cierto, descubrí por qué tuve que cambiar el orden de las llamadas para que funcione. USER_EMAIL y USER_ID están configurados en una cadena vacía al momento de la activación como parte del entorno predeterminado (eso no ocurre con user_is_admin). La solución es pasar 'overwrite = True' a setup_env, de lo contrario, las variables que ya están en el entorno se [ignoran] (https://code.google.com/p/googleappengine/source/browse/trunk/python/google/appengine /ext/testbed/__init__.py?r=381#328) lo que hace que get_current_user() siempre sea None. – gargc

9

Además de Bijan 's respuesta:

El cheque real en google.appengine.api.users se ve de esta manera:

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

La clave es, pues, para establecer la variable de entorno USER_IS_ADMIN a '1'. Esto se puede hacer de varias maneras, pero tenga en cuenta que está modificando una variable global y, por lo tanto, esto podría afectar a otro código. La clave es hacer una limpieza adecuada.

Se podría usar el Mock library en patch os.environ, use Testbed o despliegue su propia forma creativa. Prefiero usar Testbed ya que insinúa que el truco está relacionado con el apéndice. Mock no está incluido en las versiones de Python anteriores a la 3.3 por lo que esto agrega una dependencia de prueba adicional.

nota adicional: Cuando se utiliza el unittest module Yo prefiero usar en lugar de addCleanuptearDown desde limpiezas también se llaman cuando setUp falla.

Ejemplo de prueba:

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

Nota:Testbed.setup_envdebe ser llamado después deTestbed.activate. Testbed toma una instantánea de os.environ al momento de la activación, esa instantánea se restablece al desactivarla. Si se llama al Testbed.setup_env antes de la activación, se modifica el os.environ real en lugar de la instancia temporal, contaminando así de manera efectiva el medio ambiente.

Esto se comporta como debería:

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

Esto contamina el medio ambiente:

>>> 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 
0

aquí hay un par funciones de ayuda que he creado para mis pruebas basadas en respuestas aquí. Me las puse en un módulo 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) 

Ejemplo de uso (con 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()) 
Cuestiones relacionadas