web-dev-qa-db-ja.com

DjangoベースのプロジェクトでのA / Bテストについて何か考えはありますか?

DjangoベースのプロジェクトのA/Bテストを開始しました。このA/Bテストに関するベストプラクティスや有用な洞察に関する情報を入手できますか。

理想的には、新しいテストページはそれぞれ1つのパラメータで区別されます(Gmailのように)。 mysite.com/?ui=2は別のページを表示するはずです。したがって、すべてのビューについて、「ui」パラメータ値に基づいてさまざまなテンプレートをロードするデコレータを作成する必要があります。また、デコレータにテンプレート名をハードコーディングしたくありません。では、urls.pyのURLパターンはどのようになりますか?

57
None-da

コードに飛び込む前に、一歩下がってA/Bテストが何をしようとしているのかを抽象化すると便利です。テストを実施するには、正確に何が必要ですか?

  • 条件のある目標
  • 目標の条件を満たすための少なくとも2つの異なるパス
  • パスの1つに視聴者を送るためのシステム
  • テストの結果を記録するためのシステム

これを念頭に置いて、実装について考えてみましょう。

目標

Webでの目標について考えるとき、通常、ユーザーが特定のページに到達するか、特定のアクションを完了することを意味します。たとえば、ユーザーとして正常に登録したり、チェックアウトページにアクセスしたりします。

Djangoでは、いくつかの方法でモデル化できます。おそらく、ビュー内で単純に、目標に到達するたびに関数を呼び出します。

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

しかし、必要な場所にそのコードを追加する必要があるため、それは役に立ちません。さらに、プラグイン可能なアプリを使用している場合は、コードを編集してA/Bテストを追加したくないのです。

ビューコードを直接編集せずにA/B目標を導入するにはどうすればよいですか?ミドルウェアはどうですか?

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

これにより、サイトのどこにいてもA/B目標を追跡できるようになります。

目標の条件が満たされていることをどのようにして知ることができますか?実装を容易にするために、ユーザーが特定のURLパスに到達したときに、目標の条件が満たされていることを確認することをお勧めします。ボーナスとして、ビュー内で手を汚さずにこれを測定できます。ユーザーを登録する例に戻ると、ユーザーがURLパスに到達したときにこの目標が達成されたと言えます。

/登録/完了

したがって、a_b_goal_conditions_metを定義します。

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

パス

Djangoのパスについて考えるとき、異なるテンプレートを使用するというアイデアに飛びつくのは自然なことです。別の方法があるかどうかはまだ検討されていません。A/ Bテストでは、2つのページと結果を測定します。したがって、目標へのすべてのパスを拡張する単一のベースパステンプレートを定義することをお勧めします。

これらのテンプレートをどのようにレンダリングする必要がありますか?デコレータはおそらく良いスタートです-デコレータが実行時にこのパラメータを変更する可能性があるビューにパラメータtemplate_nameを含めることはDjango)のベストプラクティスです。

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

このデコレータは、ラップされた関数をイントロスペクトしてtemplate_name引数を変更するか、どこか(モデルなど)から正しいテンプレートを検索するのを見ることができます。すべての関数にデコレータを追加したくない場合は、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

また、どのビューでA/Bテストが実行されているかなどを追跡する方法を追加する必要があります。

視聴者をパスに送るためのシステム

理論的にはこれは簡単ですが、さまざまな実装があるため、どれが最適かは明確ではありません。優れたシステムでは、ユーザーをパスに沿って均等に分割する必要があることはわかっています-ハッシュメソッドを使用する必要があります-memcacheカウンターのモジュラスをパスの数で割ったものを使用できるかもしれません-もっと良い方法があるかもしれません。

テストの結果を記録するためのシステム

何人のユーザーがどのパスをたどったかを記録する必要があります-ユーザーが目標に到達したときにこの情報にアクセスする必要もあります(目標の条件を満たすためにどのパスをたどったかを言うことができる必要があります)-私たちある種のモデルを使用してデータを記録し、DjangoセッションまたはCookieのいずれかを使用して、ユーザーが目標条件を満たすまでパス情報を保持します。

最後の考え

Django-上記は完全なソリューションではありませんが、A/Bテスト用の再利用可能なフレームワークを作成するための良いスタートです。 Djangoで。

参考までに、GitHubでPaulMarの7分間のA/Bを確認することをお勧めします。これは、上記のRORバージョンです。 http://github.com/paulmars/seven_minute_abs/tree/master


更新

グーグルウェブサイトオプティマイザーのさらなる考察と調査で、上記の論理にギャップのある穴があることは明らかです。さまざまなテンプレートを使用してパスを表すことにより、ビューのすべてのキャッシュを解除します(または、ビューがキャッシュされている場合は、常に同じパスを提供します!)。パスを使用する代わりに、GWOの用語を盗み、Combinations(テンプレート変更の特定の部分)のアイデアを使用します。たとえば、サイトの<h1>タグを変更します。

このソリューションには、JavaScriptにレンダリングされるテンプレートタグが含まれます。ページがブラウザに読み込まれると、JavaScriptはサーバーにリクエストを送信し、サーバーは可能な組み合わせの1つをフェッチします。

このようにして、キャッシュを維持しながら、ページごとに複数の組み合わせをテストできます。


更新

テンプレートを切り替える余地はまだあります。たとえば、まったく新しいホームページを導入し、古いホームページとのパフォーマンスをテストしたい場合は、テンプレートの切り替え手法を使用する必要があります。覚えておくべきことは、ページのキャッシュされたバージョンのX個を切り替える方法を理解する必要があるということです。これを行うには、標準のキャッシュされたミドルウェアをオーバーライドして、要求されたURLで実行されているA/Bテストであるかどうかを確認する必要があります。次に、表示する正しいキャッシュバージョンを選択できます!!!


更新

上記のアイデアを使用して、Djangoの基本的なA/Bテスト用のプラグ可能なアプリを実装しました。あなたはそれをGithubから降りることができます:

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

95
jb.

DjangoリーンはA/Bテストに適したオプションです

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

12

提案したようにGETパラメーター(?ui=2)を使用する場合は、urls.pyにまったく触れる必要はありません。デコレータはrequest.GET['ui']を検査して、必要なものを見つけることができます。

テンプレート名のハードコーディングを回避するために、view関数からの戻り値をラップすることができますか? render_to_responseの出力を返す代わりに、(template_name, context)のタプルを返し、デコレータにテンプレート名をマングルさせることができます。このようなものはどうですか? 警告:このコードはテストしていません

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

これは本当に基本的な例ですが、それがアイデアを広めることを願っています。テンプレートコンテキストに情報を追加するなど、応答に関する他のいくつかのことを変更できます。これらのコンテキスト変数を使用して、たとえばGoogleAnalyticsなどのサイト分析と統合できます。

ボーナスとして、GETパラメータの使用をやめ、Cookieなどに基づくものに移行する場合は、将来このデコレータをリファクタリングすることができます。

更新すでに多くのビューを記述していて、それらすべてを変更したくない場合は、独自のバージョンの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})
7
Justin Voss

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

コードを使用した関数の例:

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

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

ここで何が起こるか:1。渡されたUIパラメーターはセッション変数に格納されます2.同じテンプレートが毎回ロードされますが、コンテキスト変数{{ui}}はUI IDを格納します(テンプレートを変更するために使用できます)3。ユーザーが?ui = xxなしでページに入ると、index2の場合は '?ui = 33'にリダイレクトされ、index1の場合はUI変数が0に設定されます。

3を使用してメインページからGoogleウェブサイトオプティマイザーにリダイレクトします。Googleウェブサイトオプティマイザーは、適切な?uiパラメーターを使用してメインページにリダイレクトします。

1
kolinko

これらの答えは古くなっているようです。今日、Google Analyticsは、おそらくほとんどのサイトで最も人気があり、最高の無料オプションです。 DjangoをGoogleAnalyticsと統合するためのリソースは次のとおりです。

プラグイン

ハウツー

1
Brian

Django-leanは素晴らしく見えます。私はまだそれを理解しようとしています。自分がやろうとしていたことには十分な独自のソリューションを作成することになりました。私はそれをうまくパッケージ化して、初心者にとって使いやすいようにしようとしました。それは非常に基本的です、それを試してみてください:

https://github.com/crobertsbmw/RobertsAB

1
Chase Roberts

ジャスティンの反応は正しいです...彼が最初だったので、私はあなたがそれに投票することをお勧めします。彼のアプローチは、このA/B調整が必要な複数のビューがある場合に特に役立ちます。

ただし、ビューがほんの一握りの場合は、デコレータやurls.pyの変更は必要ありません。 urls.pyファイルをそのままにしておくと...

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

... request.GETを使用して、要求されたビューバリアントを判別できます。

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

個々のA/B/C/etcビューのテンプレート名をハードコーディングしないようにする場合は、テンプレートの命名規則でそれらを規則にします(Justinのアプローチでも推奨されています)。

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')
1
Jarret Hardie

Djangoの分割テストの例を作成しました。これを見ると、役立つかもしれません-

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

あなたがそれについてどう思うか、そして私がそれをもっと役立つようにする方法を聞くのが大好きです!

0
Dan Ancona