How To Use A/Bingo
This guide explains common usage patterns for A/Bingo. It assumes you have already successfully completed installation.
- Starting a New A/B Test
- Specifying Alternatives
- Testing A/Bingo
- Multivariate Testing
- Scoring Conversions
- Seeing Results
- Ending Tests
- Statistical Significance
- Managing Identity
Starting a New A/B Test
A/B tests are always defined in code, and require no setup or configuration.
The first time code for a particular
test name executes, it does all the setup work for the test and logs the first
participant. All subsequent times the test alternatives are read straight from cache,
and the only DB access is to score the participant (a fast, optimized UPDATE).
A single test name should never be used to refer to a second test.
A/B tests can defined in controllers or views, depending on whether you are testing
program logic or merely display. The syntax is similar. Note that you can always
pass a call to ab_test a block with a single variable -- this binds the test result
to that variable. This is a programmer convenience for pretty, idiomatic Ruby code.
#A controller example:
def registration
#snipped
#We're interested in whether this user goes on to buy new
#credits or not, depending on how many freebies we give him.
@user.credits = ab_test("starting_free_credits", [0,100,200], :conversion => "buy_new_credits")
#snipped
end
#A view example without a block passed to ab_test.
#This lets you use the return value in a comparison, like "if" or "unless".
#Note an A/B test without alternatives defaults to
#flipping between true or false.
<% if (ab_test("show_mailing_list_signup")) do %>
<p>Sign up for our email list! <b>We don't spam!</b></p>
<%= check_box_tag("wants_email", user.wants_email?) %>
<% end %>
#A view example with a block passed to ab_test:
<% ab_test("call_to_action", %w{button1.jpg button2.jpg}) do |button| %>
<%= image_tag(button, :alt => "Call to action!" %>
<% end %>
#Note Rails 3 **requires** you to use the <%= block syntax, as below. Rails 2 would raise an error if you tried this.
<%= ab_test("call_to_action", %w{button1.jpg button2.jpg}) do |button| %>
<%= image_tag(button, :alt => "Call to action!" %>
<% end %>
You can also use A/Bingo in models: Abingo.test(test_name, alternatives)
All the test methods take an optional options parameter. Currently, the only
supported option is :allow_multiple_participation. A/Bingo defaults to
tracking an individual participant of a test only once. For example, if
Bob refreshes your home page 100 times, he is counted as one participant in the
login button test. If you turn multiple participation on, Bob would be counted
100 times, instead.
Back to the Table of Contents
Specifying Alternatives
A/B tests use two or more alternatives. (Currently, the built-in statistics module only provides significance testing for tests with exactly two alternatives.) You may specify alternatives via any of the following:
- (nothing): A/Bingo sets you up with true and false as alternatives
- an integer: N alternatives, from 1 to N
- a range: every element in the range is an alternative
- an array: every element in the array is an alternative
- a hash of keys to values: The keys are the alternatives, the values are integral weights. For example, {:a => 3, :b => 2} will show :a 60% of the time and :b 40% of the time.
I discourage using weights because the implementation is slower than it should be. If you want to use them before I optimize it, keep the weights small ({:a => 3, :b => 2} is the same as {:a => 60, :b => 40} in terms of behavior, but it is much more efficient.)
Back to the Table of Contents
Testing A/Bingo
Obviously, with two alternatives of your display or functionality, you'll want
to make sure both are functioning accurately. This can be sort of annoying to
do. A/Bingo lets you cheat: if params contains a variable named the same as
your test name, A/Bingo will override any automatically determined alternative
with the contents of that variable. For example, appending &test1=foo to the
URL in your browser will have A/Bingo return the alternative "foo" rather than
doing its normal choice based on your identity. Note this is purely for testing:
if you shortcircuit A/Bingo in this manner it won't collect any data for you.
This feature is disabled by default in production, because it could conceivably
cause security issues. If you want to enable it, set Abingo.options[:enable_specification] = true
This feature was inspired by Assaf Arkin's Vanity
testing framework, itself inspired by A/Bingo. Yay for open source!
Back to the Table of Contents
Multivariate Testing
This is an advanced topic. Feel free to skip it.
Multivariate testing is a more advanced version of A/B testing, in which
there are more than two alternatives. A/Bingo supports multivariate testing,
but does not currently do statistical significance tests for it, so you'll
have to run your own chi-squared tests.
Set up your test as seen above. There are
a few ways to turn a simple array of alternatives into a powerful testing tool:
A/Bingo can serialize anything which is serializable by both ActiveRecord
and the Rails cache. This means an alternative can be an array of hashes,
for example. You can use this to link two (or more) experimental variables and
see which combination is most effective.
<% ab_test("multivariate_test1", [
{:color => 'red', :text => 'Try now!},
{:color => 'blue', :text => 'Try now!'},
{:color => 'red', :text => 'Try later!},
{:color => 'blue', :text => 'Try later!'}]) do |multi| %>
<span style="color: <%= multi[:color] -%>">
<%= multi[:text] -%>
</span>
You can use this same technique to test different elements on different pages.
Remember, for a given user and test name, the alternative chosen will always
be the same. Thus, if you retrieve the alternative [:a, :c] on the first
page in your funnel, you can be confident you'll see [:a, :c] again on the seventh
page.
Smart use of this can help you "remember" e.g. promises made to the user
so that they can be mentioned again later, or help test whether the
order a user receives two bits of information matters, etc. (Is it better to
tell them the price on page one and the money back guarantee on page two, or the
other way around?)
Back to the Table of Contents
Scoring Conversions
Tracking conversions should probably be done in a controller, but may also
be done in a view. Tests are only presently allowed to have one count of
conversions associated with them. For example, a single change in copy on
your home page can be tracked to see if it increases purchases or signups, but
not both at statistics independently.
New feature: By default, A/Bingo assumes that a test
is listening for conversions with the same name as its test name. You can re-use
conversions across multiple tests by specifying them at test creation with the :conversion
parameter.
def purchase
if (...) #if user actually paid us money
bingo!("purchase")
#Presumably, the user gets something, too.
end
end
Back to the Table of Contents
Seeing Results
Most people will want to build their own dashboard for stakeholders to see, but A/Bingo ships with a default one to get you started. The dashboard also includes the ability to turn off tests through your browser.
#Create a new controller. The name is up to you -- this example #uses abingo_dashboard_controller.rb class AbingoDashboardController < ApplicationController #Declare any before_filters or similar which you need to use your authentication #for this controller. include Abingo::Controller::Dashboard end #You need to add the following to routes.rb: map.abingoTest "/abingo/:action/:id", :controller=> :abingo_dashboard #Now, simply navigate to http://www.example.com/abingo and you'll see your dashboard. #The templates can be customized to your liking in plugins/abingo/views.
Back to the Table of Contents
Ending Tests
There are three ways to end tests:
- Just delete the code and redeploy. No muss, no fuss.
- Use the dashboard to forcibly end a test. This will cause all users to see the alternative you specify on the dashboard. (This is often a prelude to later ripping the code out, but doesn't require a server redeploy.)
- Call Experiment#end_experiment!(alternative_contents) on the experiment from your own code.
Back to the Table of Contents
Statistical Significance
One key benefit of using A/B testing is that it provides hard numbers. However,
numbers can be deceptive: even if A is currently showing as better than B, this could
be simply due to random chance. Significance testing tells you when A is highly likely
to be better than B, rather than merely better due to luck.
A/Bingo comes with one statistical test built in: the one-tailed Z-score test.
If that sounds like Greek to you, you can
read up on the subject.
After you understand the rationale, feel free to poke around the implemenetation in Abingo::Statistics
so that you can incorporate it into your custom reports and dashboards. There is a method that produces
quick-and-dirty human-readable summaries to show you how it is done.
#Showing this in the console for simplicity.
#You can also see it in the built-in dashboard.
experiment = Abingo::Experiment.find_by_test_name("login_page_redesign")
experiment.describe_results_in_words
If you wish to see the results of this method, look on the sidebar for the Get the Code link.
There is a bit of an easter egg hidden behind it.
You can also easily export your data to any statistical package or custom code you prefer.
Back to the Table of Contents
Managing Identity
A/Bingo requires that you assign each user of your website an identity, which
is just a persistent, unique string. This can be done very simply, but it has
significant implications for user experience and tracking results.
The simplest solution boils down to "pick a random number, stuff it in a cookie,
done". However, this means that User Bob will have a different identity at work
and at home, and accordingly will potentially see different versions of your
website from the two locations. That may be undesirable, for example if you are
A/B testing prices or features in Bob's account.
#This is the simplest code that works.
#Goes in application.rb
before_filter :set_abingo_identity
def set_abingo_identity
if session[:abingo_identity]
Abingo.identity = session[:abingo_identity]
else
session[:abingo_identity] = Abingo.identity = rand(10 ** 10).to_i.to_s
end
end
A better solution is to integrate Bob's A/Bingo identity (which the user need never see, incidentally) with your identity management you use for login and the like
#Assumes @user has already been set somewhere #Also goes in application.rb before_filter :set_abingo_identity def set_abingo_identity Abingo.identity = @user.id end
However, this breaks down for one critical use case: tracking conversions from random website visitor to registered member of the site. They go from having no identity (or, if you were clever, a random identity) to having an identity, meaning that any conversion funnels lasting over the signup step suddenly break. To get around this you can be tricky, for example by saving their randomly assigned anonymous identity in their account at signup, and then referring to it later.
#Assumes @user has already been set somewhere
#else for logged in users.
#Also goes in application.rb
before_filter :set_abingo_identity
def set_abingo_identity
if @user
Abingo.identity = @user.abingo_identity
else
session[:abingo_identity] ||= rand(10 ** 10).to_i.to_s
Abingo.identity = session[:abingo_identity]
end
end
#tie in anonymous identity with registration
#Actual registration logic not shown
def register_new_user
@user = User.new(...)
if session[:abingo_identity]
@user.abingo_identity = session[:abingo_identity]
else
@user.abingo_identity = rand(10 ** 10).to_i.to_s
end
@user.save
end
Back to the Table of Contents
