2009-04-15 17 views
55

Acabamos de comenzar a hacer las pruebas A/B para nuestro proyecto basado en Django. ¿Puedo obtener información sobre las mejores prácticas o información útil sobre esta prueba A/B?¿Alguna idea sobre las pruebas A/B en el proyecto basado en Django?

Idealmente, cada página de prueba nueva se diferenciará con un único parámetro (como Gmail). mysite.com/?ui=2 debería dar una página diferente. Entonces, para cada vista necesito escribir un decorador para cargar diferentes plantillas basadas en el valor del parámetro 'ui'. Y no quiero codificar los nombres de las plantillas en decoradores. Entonces, ¿cómo sería el patrón urls urls.py?

Respuesta

7

Si usa los parámetros GET como suggsted (?ui=2), entonces no debería tener que tocar urls.py en absoluto. Su decorador puede inspeccionar request.GET['ui'] y encontrar lo que necesita.

Para evitar los nombres de las plantillas de codificación rígida, ¿quizás podría ajustar el valor de retorno de la función de vista? En lugar de devolver la salida de render_to_response, puede devolver una tupla de (template_name, context) y dejar que el decorador destruya el nombre de la plantilla. ¿Qué tal algo como esto? ADVERTENCIA: No he probado este código

def ab_test(view): 
    def wrapped_view(request, *args, **kwargs): 
     template_name, context = view(request, *args, **kwargs) 
     if 'ui' in request.GET: 
      template_name = '%s_%s' % (template_name, request.GET['ui']) 
      # ie, 'folder/template.html' becomes 'folder/template.html_2' 
     return render_to_response(template_name, context) 
    return wrapped_view 

Este es un ejemplo muy básico, pero espero que se le ocurre al otro lado. Puede modificar muchas otras cosas sobre la respuesta, como agregar información al contexto de la plantilla. Puede usar esas variables de contexto para integrar con su análisis del sitio, como Google Analytics, por ejemplo.

Como beneficio adicional, usted podría refactorizar este decorador en el futuro si usted decide dejar de usar parámetros GET y pasar a algo basado en cookies, etc.

actualización Si ya tiene una gran cantidad de puntos de vista por escrito , y no desea modificarlos a todos, puede escribir su propia versión de render_to_response.

def render_to_response(template_list, dictionary, context_instance, mimetype): 
    return (template_list, dictionary, context_instance, mimetype) 

def ab_test(view): 
    from django.shortcuts import render_to_response as old_render_to_response 
    def wrapped_view(request, *args, **kwargs): 
     template_name, context, context_instance, mimetype = view(request, *args, **kwargs) 
     if 'ui' in request.GET: 
      template_name = '%s_%s' % (template_name, request.GET['ui']) 
      # ie, 'folder/template.html' becomes 'folder/template.html_2' 
     return old_render_to_response(template_name, context, context_instance=context_instance, mimetype=mimetype) 
    return wrapped_view 

@ab_test 
def my_legacy_view(request, param): 
    return render_to_response('mytemplate.html', {'param': param}) 
+0

Pero esto implica la modificación de todos los puntos de vista anteriores existant ¿verdad? No quiero hacer eso. ¿Hay alguna manera alternativa de que mi antiguo código permanezca igual y esta prueba de ab se mantenga al margen? –

+0

Podría escribir su propia versión de render_to_response, supongo, que interactuaría con este decorador para lograr lo que desea. Agregaré una muestra de código a mi respuesta para demostrar. –

+0

Justin: sus dos sugerencias son excelentes. Maddy: si quieres probar A/B en un sitio completo, entonces no, vas a tener que morder la bala en algún lugar de tu cadena de solicitud/respuesta y, de forma programática o declarativa, indicar qué plantillas coinciden con los números 'ui'. –

1

La respuesta de Justin es correcta ... Recomiendo que votes por esa, ya que fue el primero. Su enfoque es particularmente útil si tiene múltiples vistas que necesitan este ajuste A/B.

Tenga en cuenta, sin embargo, que no necesita un decorador o alteraciones en urls.py, si solo tiene unas pocas vistas. Si usted dejó su archivo urls.py como es ...

(r'^foo/', my.view.here), 

... se puede utilizar para determinar request.GET la variante vista solicitada:

def here(request): 
    variant = request.GET.get('ui', some_default) 

Si se quiere evitar hardcoding plantilla nombres para el individuo a views/B/C/etc, simplemente hacen una convención en el esquema de denominación plantilla (como el enfoque de Justin también recomienda):

def here(request): 
    variant = request.GET.get('ui', some_default) 
    template_name = 'heretemplates/page%s.html' % variant 
    try: 
     return render_to_response(template_name) 
    except TemplateDoesNotExist: 
     return render_to_response('oops.html') 
+0

Eso funcionaría, pero creo que si vas a probar más de una vista, entonces mover ese código a un decorador te ahorrará mucho tipeo. –

+0

Sí, estoy de acuerdo, Justin. Editando mi respuesta porque somos muy similares, pero el tuyo es anterior. –

+0

Esto sugiere que necesito escribir un decorador individual para cada vista. Que es lo que no quiero Si ui = 2 se menciona en todo el sitio (es decir, cualquier URL que termine en? Ui = 2), carga la versión 2 de todo el sitio. Por lo tanto, se necesita un decorador genérico que cargue las plantillas de la versión 2 en cualquier url. ¿Ha quedado claro? Por lo tanto, cambiar las plantillas dinámicamente (con ui = 3, ui = 3) para cada vista debería ocurrir de forma similar. Entonces un decorador debería hacer. ¿Es eso posible? –

91

es útil para dar un paso atrás y abstracto lo que a/Testin B g está tratando de hacer antes de sumergirse en el código. ¿Qué es exactamente lo que necesitamos para realizar una prueba?

  • un objetivo que tiene una condición
  • Al menos dos caminos distintos para satisfacer la condición del Meta
  • Un sistema para el envío de los espectadores por una de las rutas de acceso
  • Un sistema para registrar los resultados de la prueba

Con esto en mente, pensemos en la implementación.

El objetivo

Cuando pensamos en una meta en la web por lo general nos referimos a que un usuario llega a una determinada página o que se complete una acción específica, por ejemplo, registrarse con éxito como un usuario o para llegar a la página de pago

En Django podríamos modelar que en un par de maneras - tal vez ingenuamente, dentro de un punto de vista, llamar a una función cada vez que un objetivo ha sido alcanzado:

def checkout(request): 
     a_b_goal_complete(request) 
     ... 

Pero eso no ayuda porque tendremos para agregar ese código en cualquier lugar que lo necesitemos; además, si utilizamos cualquier aplicación conectable, preferimos no editar su código para agregar nuestra prueba A/B.

¿Cómo podemos introducir Metas A/B sin editar directamente el código de vista? ¿Qué tal un Middleware?

class ABMiddleware: 
     def process_request(self, request): 
      if a_b_goal_conditions_met(request): 
      a_b_goal_complete(request) 

Eso nos permitiría rastrear las Metas A/B en cualquier parte del sitio.

¿Cómo sabemos que se cumplen las condiciones de un objetivo? Para facilitar la implementación, sugeriré que sepamos que un objetivo ha cumplido sus condiciones cuando un usuario llega a una ruta de URL específica. Como beneficio adicional podemos medir esto sin ensuciarnos las manos dentro de una vista. Para volver a nuestro ejemplo de registro de un usuario que podríamos decir que este objetivo se ha cumplido cuando el usuario llega a la ruta URL:

/registro/completa

Así definimos a_b_goal_conditions_met:

 a_b_goal_conditions_met(request): 
     return request.path == "/registration/complete": 

Caminos

al pensar en caminos en Django que es natural para saltar a la idea de utilizar diferentes plantillas. Si queda otro camino por explorar. En las pruebas A/B, usted hace pequeñas diferencias entre dos páginas y mide los resultados. Por lo tanto, debe ser una mejor práctica definir una única plantilla de ruta base a partir de la cual deben extenderse todas las rutas a la meta.

¿Cómo deberían renderizarse estas plantillas? Un decorador es probablemente un buen comienzo. Es una buena práctica en Django incluir un parámetro template_name en sus vistas. Un decorador podría alterar este parámetro en tiempo de ejecución.

@a_b 
    def registration(request, extra_context=None, template_name="reg/reg.html"): 
     ... 

se podía ver este decorador ya sea introspección de la función envuelta y modificar el argumento template_name o mirando las plantillas correctas de algún lugar (como un modelo).Si no queríamos añadir el decorador a todas las funciones que podríamos aplicar esto como parte de nuestra ABMiddleware:

class ABMiddleware: 
     ... 
     def process_view(self, request, view_func, view_args, view_kwargs): 
     if should_do_a_b_test(...) and "template_name" in view_kwargs: 
      # Modify the template name to one of our Path templates 
      view_kwargs["template_name"] = get_a_b_path_for_view(view_func) 
      response = view_func(view_args, view_kwargs) 
      return response 

Necesitaríamos también tenemos que añadir un poco de manera de no perder de los cuales tienen vistas A/B las pruebas se ejecutan etc.

un sistema para el envío de los espectadores por un camino

En teoría esto es fácil, pero hay gran cantidad de diferentes implementaciones de modo que no está claro cuál es el mejor. Sabemos que un buen sistema debe dividir a los usuarios de manera uniforme en el camino. Se debe usar algún método hash. Quizás se pueda usar el módulo del contador de Memcache dividido por el número de Rutas. Tal vez haya una mejor manera.

Un sistema de registro de los resultados de la prueba

necesitamos registrar el número de usuarios descendieron lo Path - también necesitaremos acceso a esta información cuando el usuario llega a la meta (necesitamos ser capaz de decir qué camino bajaron para cumplir con la Condición de la Meta) - usaremos algún tipo de Modelo (s) para registrar los datos y las Sesiones de Django o las Cookies para conservar la información de la Ruta hasta que el usuario cumpla con la Meta condición.

Pensamientos finales

me he dado un montón de pseudo código para la implementación de las pruebas A/B en Django - lo anterior es de ninguna manera una solución completa, pero un buen comienzo hacia la creación de un marco reutilizable para una/B de prueba en Django.

Como referencia, puede consultar los 7 minutos de duración de Paul Mar en GitHub: ¡es la versión ROR de los anteriores! http://github.com/paulmars/seven_minute_abs/tree/master


actualización

En una posterior reflexión e investigación de Google Website Optimizer es evidente que hay agujeros en la lógica anterior. Al usar diferentes plantillas para representar Rutas, se rompe todo el almacenamiento en caché en la vista (o si la vista se almacena en caché, ¡siempre servirá la misma ruta!). En lugar de usar Paths, en cambio, robaría la terminología de GWO y usaría la idea de Combinations, que es una parte específica de un cambio de plantilla, por ejemplo, cambiar la etiqueta <h1> de un sitio.

La solución implicaría etiquetas de plantilla que se procesarían en JavaScript. Cuando la página se carga en el navegador, JavaScript hace una solicitud a su servidor que busca una de las posibles combinaciones.

¡De esta forma puede probar múltiples combinaciones por página mientras preserva el almacenamiento en caché!


actualización

Todavía hay espacio para la plantilla de conmutación - dice, por ejemplo, se introduce una nueva página de inicio, y quiere comprobar su rendimiento frente a la antigua página de inicio - que todavía querrá usar la técnica de cambio de plantilla. Lo que hay que tener en cuenta es que tendrá que encontrar la manera de cambiar entre las X versiones de la página en caché. Para hacerlo, debe sobrescribir el middleware en caché estándar para ver si se trata de una prueba A/B que se ejecuta en la URL solicitada.¡Entonces podría elegir la versión correcta en caché para mostrar!


actualización

Usando las ideas descritas anteriormente he implementado una aplicación enchufable para pruebas básicas de A/B Django. Se puede bajar de él Github:

http://github.com/johnboxall/django-ab/tree/master

+0

Gracias Jb, realmente aprecio su solución. Permítanme intentar implementarlo y les contaré mis comentarios al respecto. –

+8

Esta es una de las mejores publicaciones que he visto en el año que llevo en este sitio. Buena cosa. – theycallmemorty

+2

Santa granada de mano de Antioquía, no te detengas hasta que esté hecho. –

1

un código basado en el de Justin Voss:

def ab_test(force = None): 
    def _ab_test(view): 
     def wrapped_view(request, *args, **kwargs): 
      request, template_name, cont = view(request, *args, **kwargs) 
      if 'ui' in request.GET: 
       request.session['ui'] = request.GET['ui'] 
      if 'ui' in request.session: 
       cont['ui'] = request.session['ui'] 
      else: 
       if force is None: 
        cont['ui'] = '0' 
       else: 
        return redirect_to(request, force) 
      return direct_to_template(request, template_name, extra_context = cont) 
     return wrapped_view 
    return _ab_test 

ejemplo de función utilizando el código:

@ab_test() 
def index1(request): 
    return (request,'website/index.html', locals()) 

@ab_test('?ui=33') 
def index2(request): 
    return (request,'website/index.html', locals()) 

Lo que sucede aquí: 1. El parámetro UI pasado se almacena en la variable de sesión 2. La misma plantilla se carga cada vez, pero una variable de contexto {{ui}} almacena la ID de UI (puede usarlo para modificar la plantilla) 3. Si el usuario ingresa a la página sin? ui = xx, en caso de index2 se lo redirecciona a '? ui = 33', en caso de index1 la variable UI se establece en 0.

Yo uso 3 para redirigir desde la página principal al Optimizador de sitios web de Google que, a su vez, redirige a la página principal con un parámetro? Ui adecuado.

1

Estas respuestas parecen obsoletas. Hoy en día, Google Analytics es probablemente la opción gratuita más popular y mejor para la mayoría de los sitios. Aquí están algunos recursos para la integración de Django con Google Analytics:

Plugins:

Cómo Tos:

0

que redactó una fracción de ejemplo las pruebas de Django que cualquier persona que mira esto podría resultar útil -

https://github.com/DanAncona/django-mini-lean

Me encantaría saber qué piensas de eso, y cómo puedo hacerlo más útil.

1

Django-lean se ve increíble. Voy a tratar de resolverlo todavía. Terminé rodando mi propia solución que es suficiente para lo que estaba tratando de hacer. He intentado empaquetarlo bien y hacerlo fácil de usar para el principiante.Es súper básico, darle una oportunidad:

https://github.com/crobertsbmw/RobertsAB

Cuestiones relacionadas