#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)
Los tests A/B son una forma de experimentar con variantes de una aplicación y determinar cuáles de ellas son más efectivas. A continuación se muestra el formulario de registro de una aplicación. Al comienzo del formulario hay un párrafo que explica lo que tiene que hacer el usuario y queremos determinar cómo de efectivo es este párrafo a la hora de convencer a los usuarios para que se registren, y para esto utilizaremos un test A/B.
El funcionamiento es el siguiente: a cada visitante de la página se le mostrará o no el párrafo de la parte superior, y tenemos que determinar qué opción da los mejores resultados. Para esto tenemos que definir un evento que, cuando ocurra, nos indicará que la opción ha tenido éxito. En nuestro caso este evento ocurrirá cuando el usuario rellene el formulario y lo envíe.
Cómo escoger la herramienta adecuada
Hay varias herramientas disponibles para hacer tests A/B. Una de las que podríamos utiliar es Google Website Optimizer. No es una tecnología específica de Rails por lo que puede usarse con cualquier tipo de aplicación web, incluso con sitios estáticos. Con esta herramienta podemos dar diferentes URLs, o utilizar JavaScript para rastrear las conversiones. Se trata de una opción bastante buena pero si queremos hacer nuestros tests sobre una aplicación Rails tendremos que usar algo que se integre dentro de la aplicación para gestionar el seguimiento propiamente dicho.
Una de las soluciones específicas de Rails más populares esVanity. Devuelve informes de resultados muy vistosos e incluye bastantes funcionalidades pero hoy no vamos a tratar de ella (si bien puede que lo hagamos en algún próximo episodio). Puede resultar un poco difícil de configurar porque por motivos de rendimuento necesita una base de datos Redis pero merece la pena echarle un vistazo para ver si se ajusta a nuestras necesidades.
El plugin que vamos a usar es A/Bingo. El DSL que trae viene bastante bien para definir experimentos y es fácil de usar. La aplicación sobre la que haremos nuestros tests está escrita con Rails 2, por lo que instalaremos el plugin con
script/plugin install git://git.bingocardcreator.com/abingo.git
en el directorio de nuestra aplicación. Una vez que haya terminado generaremos una migración con
script/generate abingo_migration
y la ejecutaremos con
rake db:migrate
Con esto se generarán dos tablas nuevas en la base de datos en la que se guardarán los resultados de nuestros tests: experiments
y alternatives
.
En el entorno de producción es recomendable que configuremos la caché en A/Bingo para que utilice algo como memcached
pero nos saltaremos esa parte porque sólo vamos a usar el entorno de desarrollo.
Nuestro primer test
Como ya hemos instalado A/Bingo podemos escribir nuestro primer test. Queremos mostrar u ocultar el párrafo de la parte superior del formulario de registro de forma que aparezca para algunos usuarios y otros no lo vean. Esto lo podemos hacer con el método 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| %> <!-- se omite el formulario --> <% end %>
El primer argumento que se pasa a ab_test
es el nombre del test, que en este caso hemos llamado signup-intro
. Si no proporcionamos más argumentos este método devolverá aleatoriamente true
o false
para los diferentes visitantes del sitio por lo que el párrafo se mostrará u ocultará dependiendo de quién cargue la página.
A continuación añadiremos el evento para poder rastrear los resultados. Queremos que se registre dicho evento cuando se guarde el usuario correctamente, para lo que modificaremos la acción create
del controlador 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
Tan sólo tenemos que invocar el método bingo!
en la acción create
y pasarle el nombre del test.
Si recargamos el formulario de registro veremos que esta vez ab_test
ha devuelto false
por lo que no vemos el párrafo de la parte de superior. Podemos recargar la página todas las veces que queramos pero el párrafo no se va a mostrar porque se ha registrado nuestra identidad (en breve explicaremos cómo).
Si completamos el formulario y lo enviamos se activará el evento que hemos definido.
Visualización de resultados
Podemos ver los resultados de nuestro test creando un controlador para ver dichos resultados. Nosotros lo llamaremos abingo_dashboard
.
script/generate controller abingo_dashboard
En este controlador sólo tenemos que incluir el módulo correspondiente de A/Bingo.
class AbingoDashboardController < ApplicationController # PENDIENTE poner autorizacion include Abingo::Controller::Dashboard end
Queda claro que si pusiésemos esta aplicación en producción no querríamos que cualquiera viese este panel de control por lo que tendríamos que añadir autorización al controlador.
También tenemos que añadir una nueva ruta para que podamos acceder a este nuevo controlador.
map.abingo_dashboard "/abingo/:action/:id", :controller => :abingo_dashboard
Una vez que esté todo ya podemos visitar http://localhost:3000/abingo y veremos nuestro panel.
Es fácil modificar las plantillas de vistas para darle los estilos que queramos a este cuadro de mandos. Sin embargo incluso con los estilos por defecto podemos ver los resultados de los experimentos, así que por ahora no los cambiaremos. Si nos fijamos en los resultados veremos que tenemos un experimento con un participante, que hemos sido nosotros mismos en la visita anterior a la página de registro, y una conversión finalizada que ocurrió cuando enviamo el formulario. El párrafo de la parte superior del formulario no se estaba mostrando por lo que el participante y su conversión aparecen en la sección de resultados "false".
Cómo se identifica a los usuarios
Antes vimos que si volvíamos a visitar el formulario de registro no veíamos nunca el párrafo porque A/Bingo siempre consideraba que nuestra identidad era la misma. Para esto tenemos que darle instrucciones sobre cómo distinguir unos usuarios de otros. Esto se hace en el controlador de aplicación escribiendo un before_filter
.
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
Hemos añadido el filtro llamado set_abingo_identity
y en dicho método hemos puesto el código que determina la identidad de cada usuario. En primer lugar se comprueba la existencia de una variable de sesión llamada abingo_identity
y si no existe la crea con un valor numérico aleatorio lo que significa que mientra el usuario mantenga su sesión será identificado como la misma persona por A/Bingo y verán o no el párrafo siempre de la misma forma.
Si tuviéramos autenticación de usuarios en nuestro aplicación querriamos que un usuario registrado viera siempre lo mismo independientemente de que cambiase su sesión o estén utilizando un ordenador o navegador distinto. Podemos cambiar el método set_abingo_identity
para que utilice el id del usuario si hay una sesión iniciada o volver al método basado en cookie de sesión para usuarios anónimos.
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
También podemos comprobar si la visita viene de una araña web y darle a cada robot la misma identidad para que los resultados no se vean afectados por estos visitantes no humanos.
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
Comprobamos si se trata de un robot comparando la cadena de agente de usuario contra una lista de nombres conocidos y si coincide, se establece el valor de identidad igual a la cadena “robot”. De esta forma todos los robots que visiten la página serán identificados como el mismo usuario.
Durante el desarrollo de la aplicación, he visitado la página de alta con diferentes identidades y me he registrado un par de veces, lo que queda reflejado en el cuadro de mandos de A/Bingo. Ahora aparecen ocho participantes, dos de los cuales vieron el párrafo y seis no.
Tests más complejos
Vamos a terminar el episodio mostrando cómo se pueden introducir múltiples opciones diferentes para un único test. El test signup_into
es un test sencillo pero también nos puede interesar tener más de dos posibilidades (por ejemplo si queremos probar con muchos títulos diferentes de página).
Es importante tener siempre en cuenta que si tenemos múltiples tests en una única página nuestros resultados pueden sesgarse hacia las opciones que aparezcan con más frecuencia. Esto es muy importante en aplicaciones en producción pero como esto es tan sólo una aplicación de ejemplo no nos preocupa.
Podemos llamar a ab_test
con dos argumentos para crear un test con múltiples opciones. El segundo parámetro será un array con las diferentes opciones que queremos testar. En nuestro caso queremos probar con tres títulos diferentes de página y podemos cambiar la página de alta para mostrar uno de ellos :
<% title ab_test("signup_title", ["Sign up", "Registration", "Free Sign up"]) %>
Con esto cada usuario que visite la página verá uno de estos títulos de forma aleatoria. Por supuesto, esto es trivial para algo tan sencillo como poner el título de la página pero si queremos utilizar el valor aleatorio más de una vez en la misma página podemos pasarle un bloque a ab_test
.
<% ab_test("signup_title", ["Sign up", "Registration", "Free Sign up"]) do |signup_title| %> <% title signup_title %> <% end %>
Es posible, si no estamos usando la última versión de A/Bingo, que veamos el mensaje “can’t modify frozen array”
. Ryan Bates ha enviado un parche para corregir este problema que ha sido aceptado, por lo que si vemos este error podemos intentarlo otra vez tras actualizar la versión del plugin.
Cuando visitemos la página de registro veremos una de las opciones del título y al página mostrará o no el párrafo de la parte superior. Pero aún nos queda una cosa por hacer. Aunque hemos configurado el test en la vista, no estamos registrando si ha tenido éxito o no en el controlador, lo que podemos hacer añadiendo otra llamada a bingo!
in la acción 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
Cabría sospechar que esto se nos puede ir de las manos si acabamos con muchos tests sobre una acción determinada. En estos casos conviene hacer una única llamada al método bingo!
con el nombre de una conversión y utilizar el nombre de dicha conversión como un argumento extra del método ab_test
en la vista, y de esta forma podemos reemplazar las dos llamadas a bingo!
por una:
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
... siempre que en la vista le pasemos el nombre de la conversión como parte del hash de argumentos.
<% 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 de la vista -->
Nótese que como el segundo test es de tipo booleano simple hemos pasado nil
como segundo argumento.
Y con esto cerramos este episodio. Los tests A/B son una forma excelente de experimentar con variantes de una aplicación web y medir el éxito de sus resultados. Desde aquí os aninamos a probarlo en vuestras aplicaciones usando Google Website Optimizer, Vanity o A/Bingo.