59 votes

Avez-vous des idées sur les tests A / B dans les projets basés sur Django?

Nous avons maintenant commencé à faire de l'A/B testing pour notre Django en fonction du projet. Puis-je obtenir de l'information sur les meilleures pratiques ou utiles à propos de ce A/B testing.

Idéalement, chaque nouvelle page de test seront différenciés avec un seul paramètre(comme Gmail). mysite.com/?ui=2 devrait donner une autre page. Ainsi, pour chaque vue j'ai besoin d'écrire un décorateur pour charger différents modèles basés sur le " ui " valeur de paramètre. Et je ne veux pas coder en dur tout les noms de modèle dans les décorateurs. Alors, comment serait urls.py modèle d'url?

102voto

jb. Points 4883

Il est utile de prendre un peu de recul et résumé ce test A/B est en train de faire avant de plonger dans le code. Quels sont les éléments que nous avons besoin d'effectuer un test?

  • Un Objectif qui a une Condition
  • Au moins deux Chemins différents pour atteindre l'Objectif de l'État
  • Un système d'envoi de téléspectateurs sur l'un des Chemins d'accès
  • Un système pour enregistrer les Résultats du test

Avec cela à l'esprit, nous allons réfléchir sur la mise en œuvre.

L'Objectif

Lorsque l'on pense à un But sur le web en général nous dire qu'un utilisateur accède à une page ou qu'ils effectuent une action spécifique, par exemple avec succès l'enregistrement en tant qu'utilisateur ou d'arriver à la page de paiement.

Dans Django nous pourrions modèle que dans un couple des manières - peut-être naïvement à l'intérieur d'un point de vue, l'appel d'une fonction à chaque fois qu'un Objectif a été atteint:

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

Mais ça n'aide pas parce que nous allons avoir à ajouter que le code partout où nous en avons besoin - et en plus, si nous utilisons tout enfichable apps nous préférerions ne pas modifier leur code à ajouter notre test A/B.

Comment peut-on introduire Une/B Objectifs sans modifier directement afficher le code? Que penser d'un Middleware?

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

Qui nous permettrait de la voie A/B Objectifs n'importe où sur le site.

Comment savons-nous que l'Objectif a été atteint? Pour la facilité de mise en œuvre, je vous suggère que nous savons qu'un But ait eu c'est les conditions sont respectées lorsqu'un utilisateur atteint une URL spécifique chemin. En bonus, on peut mesurer cela sans obtenir nos mains sales à l'intérieur d'un point de vue. Pour revenir à notre exemple de l'enregistrement d'un utilisateur, on pourrait dire que ce but a été atteint lorsque l'utilisateur atteint le chemin de l'URL:

/inscription/complet

C'est pourquoi nous définissons a_b_goal_conditions_met:

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

Les chemins de

Lors de la réflexion sur les Chemins de Django, il est naturel de sauter à l'idée de l'utilisation de différents modèles. Si il y a un autre chemin qui reste à être exploré. Dans les tests A/B vous faire de petites différences entre les deux pages et d'en mesurer les résultats. Par conséquent, il devrait être une bonne pratique pour définir un Chemin d'accès de base à partir d'un modèle tous les Chemins vers l'Objectif devrait s'étendre.

Comment doit rendre ces modèles? Un décorateur est probablement un bon point de départ - c'est une meilleure pratique dans Django afin d'inclure un paramètre template_name de vos points de vue d'un décorateur pourrait modifier ce paramètre au moment de l'exécution.

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

Vous avez pu voir cette décorateur soit l'introspection enveloppés de la fonction et de la modification de l' template_name argument ou à la recherche de la bonne des modèles à partir de quelque part (comme un Modèle). Si nous ne voulions pas ajouter le décorateur à chaque fonction, nous pouvions mettre en œuvre dans le cadre de notre 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

Nous aurions besoin également besoin d'ajouter quelque moyen de garder une trace de vues qui ont des tests A/B cours d'exécution, etc.

Un système d'envoi de téléspectateurs en bas d'un Chemin

En théorie c'est facile, mais il ya beaucoup de différentes implémentations de sorte qu'il n'est pas clair qui est le meilleur. Nous savons qu'un bon système devrait diviser les utilisateurs uniformément sur le chemin - Certaines méthode de hachage doit être utilisé - Peut-être que vous pourriez utiliser le module de memcache contre, divisé par le nombre de Chemins d'accès - peut-être il ya une meilleure façon.

Un système pour enregistrer les Résultats du Test

Nous avons besoin d'enregistrer le nombre d'utilisateurs est allé en bas de ce Chemin, nous allons également avoir besoin d'accéder à ces informations lorsque l'utilisateur atteint le but (nous devons être en mesure de dire quel Chemin ils descendirent à satisfont à la Condition de l'Objectif) - nous allons utiliser une sorte de Modèle(s) pour enregistrer les données et soit Django Sessions ou les Cookies pour conserver les informations de Chemin d'accès jusqu'à ce que l'utilisateur répond à l'Objectif de la condition.

La Fermeture De Pensées

J'ai donné beaucoup de pseudo-code pour la mise en œuvre des tests A/B dans Django - le dessus est en aucun cas une solution complète, mais un bon début vers la création d'un cadre réutilisable pour les tests A/B dans Django.

Pour référence, vous pouvez voulez regarder Paul Mar Sept Minutes A/Bs sur GitHub - c'est le ROR version de la ci-dessus! http://github.com/paulmars/seven_minute_abs/tree/master


Mise à jour

Sur la poursuite de la réflexion et une étude de Google Optimiseur de Site, il est évident qu'il y a des trous béants dans la logique ci-dessus. Par l'utilisation de différents modèles pour représenter les Chemins vous cassez la mise en cache sur la vue (ou si l'affichage est mis en cache, il sera toujours le même chemin!). Au lieu de cela, en utilisant des Chemins, je voudrais, au lieu de voler GWO de la terminologie et de l'utilisation de l'idée d' Combinations - qui est une partie spécifique d'un modèle de changement - par exemple, la modification de l' <h1> tag d'un site.

Il s'agirait de balises de modèle qui rendrait vers JavaScript. Lorsque la page est chargée dans le navigateur, le JavaScript fait une demande à votre serveur qui récupère une des Combinaisons possibles.

De cette façon, vous pouvez tester plusieurs combinaisons par page, tout en préservant la mise en cache!


Mise à jour

Il y a encore de la place pour de commutation modèle - dire par exemple d'introduire une toute nouvelle page d'accueil et que vous souhaitez tester sa performance contre l'ancienne page d'accueil - vous ne voulez toujours utiliser le modèle technique de commutation. La chose à garder à l'esprit, vous allez devoir trouver un moyen de basculer entre X nombre de versions mises en cache de la page. Pour ce faire, vous aurez besoin de remplacer la norme en cache middleware pour voir si leurs est un test A/B est en cours d'exécution sur l'URL demandée. Ensuite, il pourrait choisir la bonne version en cache les montrer!!!


Mise à jour

En utilisant les idées décrites ci-dessus, j'ai mis en place un enfichables application pour la base de test A/B de Django. Vous pouvez l'obtenir hors Github:

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

12voto

sebastian serrano Points 574

Django Lean est une bonne option pour les tests A / B

http://bitbucket.org/akoha/django-lean/wiki/Home

8voto

Justin Voss Points 2407

Si vous utilisez les paramètres comme vous suggsted (?ui=2), alors vous ne devriez pas avoir à toucher urls.py à tous. Votre décorateur d'inspecter request.GET['ui'] et de trouver ce dont il a besoin.

Pour éviter de coder en dur les noms de modèle, peut-être vous pourriez envelopper la valeur de retour de la fonction de visualisation? Au lieu de renvoyer la sortie de render_to_response, vous pourriez revenir un tuple de (template_name, context) et de laisser le décorateur mutilation du nom du modèle. Comment quelque chose comme cela? AVERTISSEMENT: je n'ai pas testé ce code

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

C'est vraiment un exemple de base, mais j'espère que ça ait l'idée de partout. Vous pouvez modifier plusieurs autres choses au sujet de l'intervention, comme l'ajout d'informations dans le modèle de contexte. Vous pouvez utiliser ces variables de contexte à intégrer à votre site analytics comme Google Analytics, par exemple.

Comme un bonus, vous pouvez refactoriser cette décorateur dans le futur si vous décidez de ne plus utiliser les paramètres GET et de passer à quelque chose de basé sur les cookies, etc.

Mise à jour Si vous avez déjà beaucoup de points de vue écrits, et vous ne voulez pas modifier tous, vous pouvez écrire votre propre version 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})

1voto

Jarret Hardie Points 36266

Justin réponse est à droite... je vous recommande de voter pour celui-là, comme il était le premier. Son approche est particulièrement utile si vous avez plusieurs points de vue qui ont besoin de ce A/B de réglage.

Notez, cependant, que vous n'avez pas besoin d'un décorateur ou d'altérations urls.py si vous avez seulement une poignée de points de vue. Si vous avez laissé votre urls.py de fichiers que est...

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

... vous pouvez utiliser la demande.Déterminer le point de vue de la variante de la demande:

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

Si vous voulez éviter de coder en dur les noms de modèle pour l'individu A/B/C/etc points de vue, il suffit de leur faire une convention dans votre modèle de schéma d'affectation de noms (comme Justin approche recommande également):

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

1voto

kolinko Points 895

Un code basé sur celui 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

exemple de la fonction en utilisant le code:

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

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

Ce qui se passe ici: 1. Le passé de l'INTERFACE utilisateur paramètre est stocké dans la variable de session 2. Le même modèle de charge à chaque fois, mais une variable de contexte {{ui}} les magasins de l'INTERFACE utilisateur id (vous pouvez l'utiliser pour modifier le modèle) 3. Si l'utilisateur entre dans la page sans ?ui=xx en cas de index2 il est redirigé vers '?ui=33', en cas de index1 l'INTERFACE utilisateur de la variable est définie sur 0.

J'utilise 3 pour la redirection de la page principale de Google Optimiseur de Site web qui à son tour renvoie à la page principale avec un bon ?l'interface utilisateur paramètre.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X