#214 A/B Testing with A/Bingo
- Download:
- source codeProject Files in Zip (127 KB)
- mp4Full Size H.264 Video (21.2 MB)
- m4vSmaller H.264 Video (12.5 MB)
- webmFull Size VP8 Video (34.8 MB)
- ogvFull Size Theora Video (29 MB)
L’ A/B testing, anche noto come split testing, è un grand metodo per testare versioni alternative di un’applicazione e capire quali di queste sia la più efficace. Di sotto è riportata la maschera di registrazione di una applicazione. In cima alla form c’è un paragrafo che spiega ciò che l’utente deve fare. Vogliamo determinare quanto sia efficace questo paragrafo nella persuasione degli utenti a registrarsi e possiamo farlo creando un A/B test.
Il modo in cui tutto ciò funziona è il seguente: ogni utente che visita la pagina vedrà o meno il parametro in cima alla pagina: possiamo usare l’A/B testing per determinare quale opzione porta ai migliori risultati. Per fare ciò abbiamo bisogno di un evento che, quando scatta, marchi l’opzione come vincente. In questo caso, l’evento sarà sollevato alla compilazione e conseguente submit della form.
Scegliere lo strumento di test opportuno
Ci sono una serie di diversi strumenti per l’A/B testing disponibili. Uno che potremmo usare è il Website Optimizerdi Google. Non è specifico di Rails, può essere usato con qualunque applicazione web, persino con siti web statici. Questo tool ci consente di fornire diversi URL per il sito o usare del JavaScript per tracciare le conversioni per voi. E’ una soluzione carina, ma se volete testare un’applicazione Rails, allora potreste voler usare qualcosa che magari si integri anche nell’applicazione stessa e che gestisca da sè il tracciamento dei comportamenti degli utenti.
<<<<<<< HEADUna delle soluzioni più popolari specifiche per Rails è Vanity. Questo strumento fornisce risultati meravigliosi ed è anche piuttosto completo a livello di funzionalità. Non lo tratteremo in questo episodio, ma lo faremo probabilmente in uno dei prossimi. Può risultare un po’ ostico da configurare, in quanto richiede un database Redis per ragioni di performance, ma vale la pena darci un’occhiata per vedere se fa al caso vostro.
=======Una delle soluzioni più popolari specifiche per Rails è Vanity. Questo strumento fornisce risultati meravigliosi ed è anche piuttosto completo a livello di funzionalità. Non lo tratteremo in questo episodio, ma lo faremo probabilmente in uno dei prossimi. Può risultare un po’ ostico da configurare, in quanto richiede un database Redis per ragioni di performance, ma vale la pena darci un’occhiata per vedere se fa al caso vostro.
>>>>>>> knightq/masterIl plugin che invece useremo in questo episodio è A/Bingo. Ha un buon DSL per la definizione degli esperimenti ed è semplice da mettere su. L’applicazione su cui eseguiremo i test è scritta in Rails 2, per cui possiamo installare questo plugin eseguendo:
script/plugin install git://git.bingocardcreator.com/abingo.git
dalla cartella della nostra applicazione. Quando termina l’esecuzione, dovremo generare una migrazione con:
script/generate abingo_migration
e poi applicarla con:
rake db:migrate
Questa migrazione genererà due nuove tabelle sul database: experiments
e alternatives
ed è in queste tabelle che verranno memorizzati i risultati dei nostri test.
In un ambiente di produzione è meglio configurare il caching di A/Bingo, ma in questo contesto possiamo saltare questa configurazione, visto che siamo in sviluppo.
Scrivere il primo test
Ora che abbiamo installato A/Bingo, possiamo scrivere il primo test. Vogliamo mostrare o meno il paragrafo in cima alla form di registrazione in modo tale che si veda per alcuni utenti, mentre per altri resti nascosto. Possiamo farlo usando il metodo ab_test
:
<% title ("Sign up") %> <% if ab_test "signup_intro" %> <p>Complete the form below to create a new user account. You will then be able to create projects and tasks, and mark them as complete when finishing them.</p> <% end %> <p>Already have an account? <%= link_to "Log in", new_user_session_path %>.</p> <% form_for @user do |form| %> <!-- form omitted --> <% end %>
Il primo argomento passato al metodo ab_test
è il nome del test, che in questo caso è “signup-intro”. Se non forniamo ulteriori argomenti, questo metodo restituirà in modo casuale true
o false
ai vari utenti che via via chiederanno la pagina, di conseguenza il paragrafo sarà mostrato o nascosto a second di chi carica la pagina.
Poi aggiungiamo un trigger di evento in modo tale che possiamo tracciare i risultati. Vogliamo che l’evento sia tracciato quando l’utente viene salvato con successo. Per questa ragione, modifichiamo la action create
sul controller UsersController
:
def create @user = User.new(params[:user]) if @user.save bingo! "signup_intro" session[:user_id] = @user.id flash[:notice] = "Thank you for signing up. You are now logged in." redirect_to root_url else render :action => 'new' end end
Nell’action create
tutto ciò che dobbiamo fare è chiamare il metodo bingo!
e passargli il nome del test quando l’utente viene correttamente salvato.
Se ricarichiamo la pagina di registrazione ora, potremo vedere che per noi ab_test
ha restituito false
, per cui non vediamo il paragrafo in cima alla pagina. Possiamo provare a ricaricare la pagina nuovamente, ma il paragrafo continuerà ad essere nascosto, dal momento che la nostra identità viene ricordata (come, ve lo faremo vedere fra poco).
Se completiamo la compilazione della form e facciamo il submit, ci registreremo con successo e per questo scateneremo un evento.
Vedere i risultati
Al momento è difficile vedere i risultati del test, ma possiamo comunque farlo creando un controller per vederli. Per fare ciò, generiamo un controller che chiamiamo abingo_dashboard
:
script/generate controller abingo_dashboard
Dentro a questo controller dobbiamo includere il module dashboard di A/Bingo:
class AbingoDashboardController < ApplicationController # TODO aggiungere logica di autorizzazione. include Abingo::Controller::Dashboard end
Ovviamente, se questa applicazione andasse in produzione, non vorremmo che chiunque potesse vedere la dashboard, per cui dovremmo implementare un qualche meccanismo di autorizzazione sul controller. Per ora comunque lasciamo solo un commento per ricordarcelo in futuro.
Dobbiamo anche aggiungere un nuovo instradamento in modo che possiamo accedere alla dashboard.
map.abingo_dashboard "/abingo/:action/:id", :controller => :abingo_dashboard
Sistemato tutto ciò, possiamo andare all’indirizzo http://localhost:3000/abingo e vedremo la dashboard:
Al momento non ci sono stili applicati alla dashboard, ma è abbastanza semplice cambiare il template della vista e darle uno stile per venire incontro all’aspetto del resto dell’applicazione. Con lo stile di default possiamo già vedere i resultati degli esperimenti, per cui non c’è bisogno di occuparsi degli stili per ora. Dando uno sguardo ai risultati, possiamo vedere che c’è un esperimento con un partecipante (si tratta della mia visita alla pagina di registrazione) ed una conversione che è avvenuta quando ho fatto correttamente il submit dei dati della form. Il paragrafo in cima alla form non era visibile all’atto della registrazione, per cui il participante e la conversione sono mostrati fra i risultati false (f).
Riconoscere gli utenti
Se rivisitassimo la form di registrazione nuovamente, continueremmo a non vedere il paragrafo sopra alla form, fintanto che A/Bingo saprà che la nostra identità è costante. Dobbiamo istruirlo per gestire le identità utente e dargli un modo per distinguere un utente da un altro. Tutto ciò viene fatto dentro l’application controller scrivendo un before_filter
:
# Filters added to this controller apply to all controllers in the application. # Likewise, all the methods added will be available for all controllers. class ApplicationController < ActionController::Base helper :all # include all helpers, all the time protect_from_forgery # See ActionController::RequestForgeryProtection for details before_filter :set_abingo_identity private def set_abingo_identity session[:abingo_identity] ||= rand(10 ** 10) Abingo.identity = session[:abingo_identity] end end
Nell’application controller abbiamo aggiunto un before filter chiamato set_abingo_identity
e nel metodo set_abingo_identity
c’è il codice che determina l’identità di ogni utente. Il metodo controlla per prima cosa l’esistenza di una variabile di sessione chiamata abingo_identity
e se non ne trova una, la crea con un valore numerico casuale. Ciò significa che, fintanto che gli utenti mantengono attive le loro sessioni, saranno sempre trattati allo stesso modo da A/Bingo e vedranno sempre la maschera come l’hanno vista la prima volta.
Se avessimo l’autenticazione nella nostra applicazione, vorremmo che la dipendenza fosse rispetto all’utenza piuttosto che alla sessione. Possiamo cambiare il metodo set_abingo_identity
in modo che usi l’id univoco di utente se l’utente corrente è autenticato, ricadendo nella gestione basata su sessione per i soli utenti anonimi:
def set_abingo_identity if current_user Abingo.identity = current_user.id else session[:abingo_identity] ||= rand(10 ** 10) Abingo.identity = session[:abingo_identity] end end
Possiamo anche controllare per vedere se il sito è stato visitato da un web crawler o un bot e dare a ciascun bot la stessa identità, in modo tale che i risultati non siano falsati da questi visitatori non umani:
def set_abingo_identity if request.user_agent =~ /\b(Baidu|Gigabot|Googlebot|libwww-perl|lwp-trivial|msnbot|SiteUptime|Slurp|WordPress|ZIBB|ZyBorg)\b/i Abingo.identity = "robot" elsif current_user Abingo.identity = current_user.id else session[:abingo_identity] ||= rand(10 ** 10) Abingo.identity = session[:abingo_identity] end end
Identifichiamo i crawler e i bot confrontandone il loro user agent contro una lista di nomi noti corrispondenti a questo genere di navigatori e se riscontriamo un match impostiamo l’identità a “robot”. In questo modo tutti i bot che visitano la pagina saranno considerati come un unico utente.
Mentre scrivevo questa applicazione, ho visitato la pagina di registrazione con una serie di differenti identità portando a termine la registrazione un paio di volte. Questo mio comportamento è ora riflesso nella dashboard di A/Bingo. Ci sono ora infatti otto partecipanti, due dei quali che hanno visto il paragrafo nella pagina di registrazione e sei che non l’hanno visto:
Test più complessi
Concludiamo l’episodio mostrandovi come fornire più alternative per singolo test. Il test signup_into
è un semplice test booleano, ma se volessimo fornire più di due diverse alternative, per esempio mostrando una serie di devrsi titoli di pagina, potremmo fare anche questo.
Vale la pena ricordarsi che avere test multipli su una singola pagina può incasinare i risultati, dal momento che questi possono derivare da quelle opzioni che vengono mostrate più frequentemente. Per un’applicazione di produzione tutto ciò è importante, ma dal momento che questa è solo un’applicazione di esempio, in realtà non conta molto.
Per creare un test con opzioni multiple, possiamo chiamare la ab_test
con due argomenti, il secondo dei quali è un array delle diverse opzioni che vogliamo testare. Nello specifico, vogliamo testare tre diversi titoli di pagina. Possiamo cambiare la pagina per mostrarne uno dei tre, usando:
<% title ab_test("signup_title", ["Sign up", "Registration", "Free Sign up"]) %>
Messo a posto ciò, ogni utente che visiterà la pagina vedrà a caso uno di questi tre titoli. Questo è sufficientemente accettabile per qualcosa di semplice come il titolo di una pagina, ma se volessimo usare gli stessi valori casuali più di una volta sulla pagina, lo potremmo fare utilizzando il metodo ab_test
con un blocco:
<% ab_test("signup_title", ["Sign up", "Registration", "Free Sign up"]) do |signup_title| %> <% title signup_title %> <% end %>
Provando questo codice, potreste vedere un errore che dice “can’t modify frozen array” nel caso in cui non si stia usando l’ultima versione di A/Bingo. Ryan Bates ha reso disponibile una patch per questo problema che sembra sia stata inclusa direttamente nel codice sorgente del progetto, per cui se vedete questo errore, provate a fare l’upgrade del plugin e a riprovare.
Visitando ora la pagina di registrazione, vedremo uno dei titoli relativi ad una delle tre opzioni e la pagina mostrerà o meno il paragrafo in testata. C’è ancora una cosa da sistemare, tuttavia. Anche se abbiamo configurato i test nella vista, non ne stiamo registrando i successi sul controller. Lo possiamo fare semplicemente aggiungendo un’altra chiamata a bingo!
nella action create:
def create @user = User.new(params[:user]) if @user.save bingo! "signup_intro" bingo! "signup_title" session[:user_id] = @user.id flash[:notice] = "Thank you for signing up. You are now logged in." redirect_to root_url else render :action => 'new' end end
Se abbiamo molti test in una determinata action, tutto ciò può, diventare poco manutenibile. Piuttosto, dunque, potremmo fare una sola chiamata a bingo!
, col nome di una conversione e usare il nome di tale conversione come argomento extra da passare al metodo ab_test
nella vista.
Se abbiamo molti test in una determinata action, tutto ciò può, diventare poco manutenibile. Piuttosto, dunque, potremmo fare una sola chiamata a bingo!
, col nome di una conversione e usare il nome di tale conversione come argomento extra da passare al metodo ab_test
nella vista.
Per cui, possiamo sostituire nel controller le due invocazioni a bingo! con una sola:
def create @user = User.new(params[:user]) if @user.save bingo! "signup" session[:user_id] = @user.id flash[:notice] = "Thank you for signing up. You are now logged in." redirect_to root_url else render :action => 'new' end end
Mentre nella vista possiamo passare il nome della conversione come parte di un hash di argomenti:
<% ab_test("signup_title", ["Sign up", "Registration", "Free Sign up"], :conversion => "signup") do |signup_title| %> <% title signup_title %> <% end %> <% if ab_test "signup_intro", nil, :conversion => "signup" %> <!-- resto della vista -->
Si noti che, dal momento che il secondo test è un semplice booleano, gli abbiamo passato nil
come secondo argomento.
E’ tutto per questo episodio. L’A/B testing fornisce un ottimo metodo per sperimentare versioni diverse di maschere in un’applicazione web e tracciare il tasso di successo dei risultati. Vi esorto a provarlo nelle vostre applicazioni, o usando Google Website Optimizer o Vanity o A/Bingo.