2009-04-15 5 views
55

Właśnie rozpoczęliśmy wykonywanie testów A/B dla naszego projektu opartego na Django. Czy mogę uzyskać informacje na temat najlepszych praktyk lub przydatnych spostrzeżeń na temat tego testu A/B.Jakieś przemyślenia na temat testów A/B w projekcie opartym na Django?

Idealnie każda nowa strona testowa będzie różnicowana za pomocą jednego parametru (tak jak Gmail). mysite.com/?ui=2 powinien dać inną stronę. Tak więc dla każdego widoku muszę napisać dekorator, aby załadować różne szablony w oparciu o wartość parametru "ui". I nie chcę twardo kodować żadnych nazw szablonów w dekoratorach. Jak będzie wyglądać URL urls.py?

Odpowiedz

7

Jeśli użyjesz parametrów GET, jak sugerujesz (?ui=2), nie powinieneś w ogóle dotykać urls.py. Twój dekorator może sprawdzić request.GET['ui'] i znaleźć to, czego potrzebuje.

Aby uniknąć nazw szablonów hardcoding, może można zawinąć zwracaną wartość z funkcji widoku? Zamiast zwracania danych wyjściowych render_to_response, możesz zwrócić krotkę z (template_name, context) i pozwolić dekoratorowi zaplątać nazwę szablonu. Co powiesz na coś takiego? UWAGA: Nie testowałem ten kod

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 

Jest to bardzo prosty przykład, ale mam nadzieję, że dostanie się pomysł poprzek. Można zmodyfikować kilka innych kwestii związanych z odpowiedzią, takich jak dodanie informacji do kontekstu szablonu. Możesz użyć tych zmiennych kontekstowych do zintegrowania z analizą witryny, na przykład Google Analytics.

Jako bonus, można byłaby ten dekorator w przyszłości, jeśli zdecydujesz się zatrzymać za pomocą parametrów GET i przejść do czegoś w oparciu o pliki cookie, itp

Aktualizacja Jeśli masz już wiele poglądów napisane i nie chcesz modyfikować ich wszystkich, możesz napisać własną wersję 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

Ale to wymaga modyfikacji wszystkich poprzednich istnial widoki prawda? Nie chcę tego robić. Czy istnieje alternatywny sposób, aby mój stary kod pozostał tak jak on i to ab-testowanie stoi na szczycie tego? –

+0

Można napisać własną wersję render_to_response, jak przypuszczam, która wchodzi w interakcje z tym dekoratorem, aby osiągnąć to, co chcesz. Dodam próbkę kodu do mojej odpowiedzi, aby zademonstrować. –

+0

Justin: obie sugestie są doskonałe. Maddy: jeśli chcesz A/B przetestować całą witrynę, to nie, będziesz musiał ugryźć bullet gdzieś w łańcuchu żądania/odpowiedzi i programowo, lub deklaratywnie, wskazać, które szablony odpowiadają numerom "ui". –

1

Odpowiedź Justina jest właściwa ... Polecam głosować na tę osobę, tak jak był pierwszy. Jego podejście jest szczególnie przydatne, jeśli masz wiele widoków, które wymagają tej korekty A/B.

Pamiętaj jednak, że nie potrzebujesz dekoratora ani żadnych zmian w urls.py, jeśli masz zaledwie kilka widoków. Jeśli lewy plik urls.py jak ...

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

... można użyć request.GET określić żądany wariant widok:

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

jeśli chcesz uniknąć hardcoding szablonu nazwy poszczególnych a views/B/C/etc, tylko uczynić je konwencja w szablonie schematu nazewnictwa (jako podejście Justina zaleca również):

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

To zadziała, ale myślę, że jeśli zamierzasz przetestować więcej niż jeden widok, przeniesienie tego kodu do dekoratora zaoszczędzi wiele pisania. –

+0

Tak, zgadzam się, Justin. Edytuję moją odpowiedź, ponieważ jesteśmy bardzo podobni, ale twoja jest wcześniejsza. –

+0

Sugeruje to, że muszę napisać indywidualny dekorator dla każdego widoku. Czego nie chcę. Jeśli ui = 2 wspomniano w całej witrynie (co oznacza, że ​​URL kończy się na? Ui = 2), ładuje wersję 2 całej witryny. Dlatego potrzebny jest ogólny dekorator, który ładuje szablony wersji 2 do dowolnego adresu URL. Czy wyraziłem się jasno? Tak więc zmiana szablonów dynamicznie (przy ui = 3, ui = 3) dla każdego widoku powinna przebiegać w podobny sposób. Tak powinien zrobić jeden dekorator. Czy to jest możliwe? –

91

warto jest zrobić krok do tyłu i abstrakcyjny co a/B testin g próbuje zrobić zanim zanurzy się w kodzie. Czego dokładnie potrzebujemy do przeprowadzenia testu?

  • cel, który ma warunek
  • Przynajmniej dwie odrębne ścieżki aby osiągnąć cel Condition
  • system do wysyłania widzów w dół jednej ze ścieżek
  • system do zapisywania wyników testu

Mając to na uwadze, pomyślmy o wdrożeniu.

Cel

Kiedy myślimy o cel w internecie zazwyczaj mamy na myśli, że użytkownik osiągnie pewną stronę lub że ukończenia określonego działania, na przykład z powodzeniem rejestracji jako użytkownik lub uzyskanie do strona kasy.

W Django mogliśmy model, który w kilka sposobów - być może naiwnie wewnątrz widoku, wywołanie funkcji ilekroć cel został osiągnięty:

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

Ale to nie pomaga, bo będziemy mieli aby dodać ten kod wszędzie tam, gdzie go potrzebujemy - i jeśli używamy dowolnych wtyczek, których nie chcemy edytować, aby dodać nasz test A/B.

Jak wprowadzić cele A/B bez bezpośredniego edytowania kodu widoku? A co z oprogramowaniem pośredniczącym?

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

To pozwoli nam śledzić cele A/B w dowolnym miejscu na stronie.

Skąd wiemy, że warunki celu zostały spełnione? Aby ułatwić implementację, zasugerujemy, że wiemy, że Cel spełnił warunki, gdy użytkownik dotrze do określonej ścieżki URL. Jako bonus możemy zmierzyć to bez brudzenia rąk w widoku. Aby powrócić do naszego przykładu z rejestracji użytkownika, moglibyśmy powiedzieć, że cel ten został osiągnięty, gdy użytkownik osiągnie ścieżkę URL:

/rejestracji/pełny

więc zdefiniować a_b_goal_conditions_met:

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

Ścieżki

myśląc o Ścieżki w Django to naturalne, aby przejść do idei przy użyciu różnych szablonów. Czy jest jeszcze inny sposób, pozostaje do zbadania. W testach A/B robisz małe różnice między dwiema stronami i mierzysz wyniki. Dlatego najlepszym rozwiązaniem jest zdefiniowanie pojedynczego szablonu ścieżki podstawowej, z którego powinny rozciągać się wszystkie ścieżki do celu.

Jak wyrenderować te szablony? Najprawdopodobniej dobry jest dekorator - w Django najlepiej jest dodać parametr template_name do swoich widoków, aby dekorator mógł zmienić ten parametr w czasie wykonywania.

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

Można zobaczyć ten dekorator albo introspekcję funkcję zawinięte i modyfikując template_name argumentu lub patrząc odpowiednie szablony z gdzieś (jak model).Jeśli nie chcemy, aby dodać dekorator do każdej funkcji możemy zaimplementować to jako część naszego 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 

Musielibyśmy też trzeba dodać jakiś sposób, aby śledzić, które widoki mają A/B testy uruchomione itp

system do wysyłania widzów ścieżką

teoretycznie jest to łatwe, ale istnieje wiele różnych implementacjach więc nie jest jasne, który z nich jest najlepszy. Wiemy, że dobry system powinien dzielić użytkowników równo po ścieżce - Należy użyć jakiejś metody hashowania - Być może mógłbyś użyć modułu licznika memcache podzielonego przez liczbę ścieżek - być może jest lepszy sposób.

System do zapisywania wyników testu

Musimy nagrać ilu użytkowników zszedł co Path - będziemy również potrzebować dostępu do tych informacji, gdy użytkownik osiągnie cel (musimy być w stanie powiedzieć, jaką Ścieżkę doszli, aby spełnić Warunek Celu) - użyjemy jakiegoś Modelu (-ów) do zapisywania danych oraz Django Sessions lub Cookies, aby zachować informacje o ścieżce, dopóki użytkownik nie dotrze do celu stan.

Zamykanie Thoughts

Dałem dużo pseudo kod do realizacji testów A/B w Django - powyższe nie jest bynajmniej kompletne rozwiązanie, ale dobry start w kierunku stworzenia ram dla wielokrotnego użytku/B w Django.

Dla odniesienia możesz chcieć spojrzeć na Siedem minut A/B Paula Mara na GitHub - jest to wersja ROR powyższego! http://github.com/paulmars/seven_minute_abs/tree/master


Aktualizacja

W dalszej refleksji i dochodzenie Google Website Optimizer to jest oczywiste, że istnieją dziury w powyższej logiki. Używając różnych szablonów do reprezentowania ścieżek, przełamujesz buforowanie w widoku (lub jeśli widok jest buforowany, zawsze będzie wyświetlał tę samą ścieżkę!). Zamiast tego, używając Ścieżek, zamiast tego kradnę terminologię GWO i używam idei Combinations - czyli jednej konkretnej części zmiany szablonu - na przykład zmieniając znacznik strony na <h1>.

Rozwiązanie obejmowałoby tagi szablonów, które renderowałyby się do JavaScript. Kiedy strona jest ładowana do przeglądarki, JavaScript przesyła żądanie do twojego serwera, który pobiera jedną z możliwych Kombinacji.

W ten sposób można przetestować wiele kombinacji na stronę, zachowując buforowanie!


Aktualizacja

Wciąż jest miejsce dla przełączania szablonu - powiedzmy na przykład pan wprowadzić zupełnie nową stronę główną i chce przetestować to występ przeciwko starym głównej - ty, że nadal chcesz używać technika przełączania szablonów. Należy pamiętać o tym, że trzeba będzie wymyślić sposób przełączania się między X liczby buforowanych wersji strony. Aby to zrobić, należy zastąpić standardowe buforowane oprogramowanie pośredniczące, aby sprawdzić, czy jest to test A/B działający pod żądanym adresem URL.Wtedy może wybrać poprawną wersję z pamięci podręcznej do pokazania !!!


Aktualizacja

Korzystanie idee opisane powyżej mam wdrożył aplikację podłączany za podstawowy testowania A/B Django. Można dostać go GitHub:

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

+0

Dziękuję Jb, naprawdę doceniam twoje rozwiązanie. Pozwól, że spróbuję to zaimplementować, a ja dam ci znać moją opinię na ten temat. –

+8

Jest to jeden z najlepszych postów, jakie widziałem w roku, w którym byłem na tej stronie. Świetna sprawa. – theycallmemorty

+2

Święty granat ręczny Antiochii, nie zatrzymuj się, dopóki nie zostanie zrobiony. –

1

kod na podstawie jednego 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 

przykładowa funkcja z kodem:

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

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

Co tutaj: 1. Podany parametr UI jest przechowywany w zmiennej sesji 2. Ten sam szablon ładuje się za każdym razem, ale zmienna kontekstowa {{ui}} przechowuje identyfikator UI (można go użyć do zmodyfikuj szablon) 3. Jeśli użytkownik wejdzie na stronę bez? ui = xx, to w przypadku indeksu2 jest przekierowywany na "? ui = 33", w przypadku indeksu1 zmienna UI jest ustawiona na 0.

Używam 3, aby przekierować ze strony głównej do Optymalizatora witryny Google, który z kolei przekierowuje z powrotem na stronę główną z odpowiednim parametrem? Ui.

1

Odpowiedzi wydają się nieaktualne. W dzisiejszych czasach Google Analytics jest prawdopodobnie najbardziej popularną i najlepszą darmową opcją dla większości witryn. Oto niektóre zasoby dla integracji Django z Google Analytics:

Wtyczki:

jak tos:

1

Django-lean wygląda niesamowicie. Spróbuję to rozgryźć. Skończyłem przetaczać własne rozwiązanie, które wystarcza do tego, co próbowałem zrobić. Próbowałem go ładnie spakować i ułatwić korzystanie z niego początkującym użytkownikom.To bardzo prosty, spróbować:

https://github.com/crobertsbmw/RobertsAB

Powiązane problemy