the-official-groupme-ab-testing-solution 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +7 -0
- data/Gemfile.lock +20 -0
- data/README.md +77 -0
- data/Rakefile +8 -0
- data/lib/ab.rb +17 -0
- data/lib/ab/alternative.rb +66 -0
- data/lib/ab/interface.rb +42 -0
- data/lib/ab/test.rb +54 -0
- data/spec/ab/alternative_spec.rb +100 -0
- data/spec/ab/interface_spec.rb +114 -0
- data/spec/ab/test_spec.rb +82 -0
- data/spec/spec_helper.rb +6 -0
- metadata +91 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
diff-lcs (1.1.2)
|
5
|
+
redis (2.2.0)
|
6
|
+
rspec (2.5.0)
|
7
|
+
rspec-core (~> 2.5.0)
|
8
|
+
rspec-expectations (~> 2.5.0)
|
9
|
+
rspec-mocks (~> 2.5.0)
|
10
|
+
rspec-core (2.5.1)
|
11
|
+
rspec-expectations (2.5.0)
|
12
|
+
diff-lcs (~> 1.1.2)
|
13
|
+
rspec-mocks (2.5.0)
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
ruby
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
redis
|
20
|
+
rspec
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# The Official GroupMe AB Testing Solution
|
2
|
+
|
3
|
+
Simple AB testing with Redis.
|
4
|
+
|
5
|
+
## USAGE
|
6
|
+
|
7
|
+
Define an AB test:
|
8
|
+
|
9
|
+
AB.define :new_twitter, "Show new Twitter or old Twitter to logged in users" do
|
10
|
+
alternative(:new) { "new-twitter.erb" }
|
11
|
+
alternative(:old) { "old-twitter.erb" }
|
12
|
+
end
|
13
|
+
|
14
|
+
Currently, identity of experiment participants is determined by an integer you
|
15
|
+
pass when the participant views the experiment. Use `AB.test` to show always show
|
16
|
+
a participant a consistent experience.
|
17
|
+
|
18
|
+
class TwitterController
|
19
|
+
def show
|
20
|
+
render :template => AB.test(:new_twitter, current_user.id)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
If you don't have a current user, you can set a cookie or something:
|
25
|
+
|
26
|
+
class HomepageController
|
27
|
+
def index
|
28
|
+
identifier = session[:ab_identifier] ||= rand(100)
|
29
|
+
render :template => AB.test(:home_page, identifier)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
To track conversions, use `AB.track`, passing the participant's identifier:
|
34
|
+
|
35
|
+
class TwitterController
|
36
|
+
def spend_dollahs
|
37
|
+
AB.track(:new_twitter, current_user.id)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
or with a cookie:
|
42
|
+
|
43
|
+
class HomepageController
|
44
|
+
def signup
|
45
|
+
AB.test(:home_page, session[:ab_identifier])
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
To get at your results, you can get a test, and call `results`.
|
50
|
+
|
51
|
+
AB.get(:home_page).results
|
52
|
+
|
53
|
+
## Testing
|
54
|
+
|
55
|
+
In tests, if you want to guarantee a certain alternative will be shown,
|
56
|
+
you can do so by specifying it with `use`
|
57
|
+
|
58
|
+
it "should say 'NEW TWITTER' showing new Twitter" do
|
59
|
+
AB.test(:new_twitter).use(:new) do
|
60
|
+
get "/twitter/show"
|
61
|
+
page.should have_content("NEW TWITTER")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
## Redis
|
66
|
+
|
67
|
+
This gem relies on the redis gem, it looks for a connection by just
|
68
|
+
booting up a Redis object with `Redis.new`. If you want to connect to
|
69
|
+
an alternative redis, you can assign it to `AB.redis`.
|
70
|
+
|
71
|
+
AB.redis = Redis.new('redis://something-weird')
|
72
|
+
|
73
|
+
### TODO
|
74
|
+
|
75
|
+
* More interesting statistics, including relevance information
|
76
|
+
* Pretty printing of stats to the command line
|
77
|
+
* Pretty web interface
|
data/Rakefile
ADDED
data/lib/ab.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
$LOAD_PATH << File.dirname(__FILE__)
|
2
|
+
|
3
|
+
require "ab/test"
|
4
|
+
require "ab/interface"
|
5
|
+
require "ab/alternative"
|
6
|
+
|
7
|
+
module AB
|
8
|
+
VERSION = "0.0.1"
|
9
|
+
|
10
|
+
def self.redis=(redis)
|
11
|
+
@redis = redis
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.redis
|
15
|
+
@redis ||= Redis.new
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module AB
|
2
|
+
class Alternative
|
3
|
+
attr_reader :name
|
4
|
+
attr_accessor :range
|
5
|
+
|
6
|
+
def initialize(test, name, options = {}, &value_proc)
|
7
|
+
@test, @name, @options, @value_proc = test, name, options, value_proc
|
8
|
+
@test.update_percentage_range(self) if percent
|
9
|
+
end
|
10
|
+
|
11
|
+
def percent
|
12
|
+
@options[:percent]
|
13
|
+
end
|
14
|
+
|
15
|
+
def value
|
16
|
+
@value ||= @value_proc.call
|
17
|
+
end
|
18
|
+
|
19
|
+
def views_key
|
20
|
+
@views_key ||= "ab:#{@test.name}:#{@name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def view!(identity)
|
24
|
+
return if identity.nil?
|
25
|
+
return if redis.zscore(views_key, identity)
|
26
|
+
score = Time.now.to_i
|
27
|
+
redis.zadd(views_key, score, identity)
|
28
|
+
end
|
29
|
+
|
30
|
+
def views
|
31
|
+
redis.zcard(views_key)
|
32
|
+
end
|
33
|
+
|
34
|
+
def conversions_key
|
35
|
+
@conversions_key ||= views_key + ":conversions"
|
36
|
+
end
|
37
|
+
|
38
|
+
def track_conversion!(identity)
|
39
|
+
return if identity.nil?
|
40
|
+
return if redis.zscore(conversions_key, identity)
|
41
|
+
return unless redis.zscore(views_key, identity)
|
42
|
+
score = Time.now.to_i
|
43
|
+
redis.zadd(conversions_key, score, identity)
|
44
|
+
end
|
45
|
+
|
46
|
+
def conversions
|
47
|
+
redis.zcard(conversions_key)
|
48
|
+
end
|
49
|
+
|
50
|
+
def results
|
51
|
+
{
|
52
|
+
:name => name,
|
53
|
+
:value => value,
|
54
|
+
:views => views,
|
55
|
+
:conversions => conversions,
|
56
|
+
:percent => percent
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def redis
|
63
|
+
AB.redis
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
data/lib/ab/interface.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
module AB
|
2
|
+
class << self
|
3
|
+
def define(name, description, &block)
|
4
|
+
raise ArgumentError, "#{name.inspect} is already an AB test" if tests.has_key?(name)
|
5
|
+
t = AB::Test.new(name, description)
|
6
|
+
t.instance_eval(&block) if block_given?
|
7
|
+
t.verify_percentages
|
8
|
+
tests[name] = t
|
9
|
+
end
|
10
|
+
|
11
|
+
def get(name)
|
12
|
+
tests[name]
|
13
|
+
end
|
14
|
+
|
15
|
+
def track(name, identity)
|
16
|
+
test = get(name)
|
17
|
+
alternative = test.get_alternative(identity)
|
18
|
+
begin
|
19
|
+
alternative.track_conversion!(identity)
|
20
|
+
rescue Timeout::Error
|
21
|
+
# Don't track data if redis is down
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def test(name, identity)
|
26
|
+
test = get(name)
|
27
|
+
alternative = test.get_alternative(identity)
|
28
|
+
begin
|
29
|
+
alternative.view!(identity)
|
30
|
+
rescue Timeout::Error
|
31
|
+
# Don't track data if redis is down
|
32
|
+
end
|
33
|
+
alternative.value
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def tests
|
39
|
+
@tests ||= {}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/ab/test.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module AB
|
2
|
+
class Test
|
3
|
+
attr_reader :name, :description, :alternatives
|
4
|
+
|
5
|
+
def initialize(name, description)
|
6
|
+
@name, @description, @alternatives, @last_percent= name, description, [], 0
|
7
|
+
end
|
8
|
+
|
9
|
+
def alternative(name, options={}, &value_proc)
|
10
|
+
alternative = AB::Alternative.new(self, name, options, &value_proc)
|
11
|
+
alternatives << alternative
|
12
|
+
alternative
|
13
|
+
end
|
14
|
+
|
15
|
+
def use(name)
|
16
|
+
begin
|
17
|
+
@using = alternatives.detect { |alternative| alternative.name == name }
|
18
|
+
yield
|
19
|
+
ensure
|
20
|
+
@using = nil
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_alternative(identity)
|
25
|
+
return @using if @using
|
26
|
+
|
27
|
+
if identity.nil?
|
28
|
+
return alternatives.first
|
29
|
+
end
|
30
|
+
|
31
|
+
if alternatives.any?(&:percent)
|
32
|
+
alternatives.detect { |alternative| alternative.range.include?(identity % 100) }
|
33
|
+
else
|
34
|
+
alternatives[identity % alternatives.size]
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def update_percentage_range(alternative)
|
39
|
+
percent = alternative.percent - 1
|
40
|
+
alternative.range = @last_percent..(@last_percent + percent)
|
41
|
+
@last_percent = alternative.range.last + 1
|
42
|
+
end
|
43
|
+
|
44
|
+
def verify_percentages
|
45
|
+
return unless alternatives.any?(&:percent)
|
46
|
+
raise ArgumentError.new("Some alternatives are missing percentages") unless alternatives.all?(&:percent)
|
47
|
+
raise ArgumentError.new("Percentages must add up to 100") unless @last_percent == 100
|
48
|
+
end
|
49
|
+
|
50
|
+
def results
|
51
|
+
alternatives.map(&:results)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AB::Alternative do
|
4
|
+
before do
|
5
|
+
@test = AB::Test.new(:home_page, "Red or blue home page")
|
6
|
+
AB.redis.flushall
|
7
|
+
end
|
8
|
+
|
9
|
+
describe "#value" do
|
10
|
+
it "returns the result of the value_proc" do
|
11
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
12
|
+
alternative.value.should == "red.erb"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "views_key" do
|
17
|
+
it "returns the experiments namespace with the test name and alternative name" do
|
18
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
19
|
+
alternative.views_key.should == "ab:home_page:red"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "view!" do
|
24
|
+
it "tracks the view (once per identity)" do
|
25
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
26
|
+
|
27
|
+
running {
|
28
|
+
alternative.view! 123
|
29
|
+
}.should change { alternative.views }
|
30
|
+
|
31
|
+
running {
|
32
|
+
alternative.view! 123
|
33
|
+
}.should_not change { alternative.views }
|
34
|
+
end
|
35
|
+
|
36
|
+
it "does not update timestamp" do
|
37
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
38
|
+
AB.redis.zadd(alternative.views_key, 1000, 123)
|
39
|
+
alternative.view! 123
|
40
|
+
AB.redis.zscore(alternative.views_key, 123).should == "1000"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "does not track the view if the identity is nil" do
|
44
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
45
|
+
running {
|
46
|
+
alternative.view! nil
|
47
|
+
}.should_not change { AB.redis.dbsize }
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "views" do
|
52
|
+
it "returns the number of views tracked" do
|
53
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
54
|
+
alternative.views.should == 0
|
55
|
+
alternative.view! 123
|
56
|
+
alternative.views.should == 1
|
57
|
+
alternative.view! 789
|
58
|
+
alternative.views.should == 2
|
59
|
+
alternative.view! 123
|
60
|
+
alternative.views.should == 2
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
describe "conversions_key" do
|
65
|
+
it "returns views_key with :conversions suffix" do
|
66
|
+
alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
67
|
+
alternative.conversions_key.should == "ab:home_page:red:conversions"
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
describe "track_conversion!" do
|
72
|
+
before do
|
73
|
+
@alternative = AB::Alternative.new(@test, :red) { "red.erb" }
|
74
|
+
end
|
75
|
+
|
76
|
+
it "tracks the conversion (once per identity)" do
|
77
|
+
@alternative.view! 123
|
78
|
+
|
79
|
+
running {
|
80
|
+
@alternative.track_conversion! 123
|
81
|
+
}.should change { @alternative.conversions }.by(1)
|
82
|
+
|
83
|
+
running {
|
84
|
+
@alternative.track_conversion! 123
|
85
|
+
}.should_not change { @alternative.conversions }
|
86
|
+
end
|
87
|
+
|
88
|
+
it "does not track conversion if identity did not view alternative" do
|
89
|
+
running {
|
90
|
+
@alternative.track_conversion! 123
|
91
|
+
}.should_not change { @alternative.conversions }
|
92
|
+
end
|
93
|
+
|
94
|
+
it "does not track the conversion if the identity is nil" do
|
95
|
+
running {
|
96
|
+
@alternative.track_conversion! nil
|
97
|
+
}.should_not change { AB.redis.dbsize }
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require "spec/spec_helper"
|
2
|
+
|
3
|
+
describe AB do
|
4
|
+
after do
|
5
|
+
AB.send(:tests).delete(:home_page)
|
6
|
+
AB.redis.flushall
|
7
|
+
end
|
8
|
+
|
9
|
+
describe ".define" do
|
10
|
+
it "defines an AB test" do
|
11
|
+
AB.define :home_page, "Big red button or big blue button versions" do
|
12
|
+
alternative(:red) { "red.erb" }
|
13
|
+
alternative(:blue) { "blue.erb" }
|
14
|
+
end
|
15
|
+
|
16
|
+
test = AB.get(:home_page)
|
17
|
+
test.name.should == :home_page
|
18
|
+
test.description.should == "Big red button or big blue button versions"
|
19
|
+
test.should have(2).alternatives
|
20
|
+
red_alternative = test.alternatives.first
|
21
|
+
red_alternative.name.should == :red
|
22
|
+
red_alternative.value.should == "red.erb"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "raises when you try to define the same test twice" do
|
26
|
+
AB.define(:home_page, "Big red button")
|
27
|
+
running {
|
28
|
+
AB.define(:home_page, "Other big red button")
|
29
|
+
}.should raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "can set a percentage for alternatives" do
|
33
|
+
AB.define :home_page, "Red button or blue button" do
|
34
|
+
alternative(:red, :percent => 10) { "red.erb" }
|
35
|
+
alternative(:red, :percent => 90) { "blue.erb" }
|
36
|
+
end
|
37
|
+
|
38
|
+
0.upto(9) { |i| AB.test(:home_page, i).should == "red.erb" }
|
39
|
+
10.upto(99) { |i| AB.test(:home_page, i).should == "blue.erb" }
|
40
|
+
end
|
41
|
+
|
42
|
+
it "explodes if percentage sum does not equal 100" do
|
43
|
+
running {
|
44
|
+
AB.define :home_page, "Red button or blue button" do
|
45
|
+
alternative(:red, :percent => 10) { "red.erb" }
|
46
|
+
alternative(:red, :percent => 80) { "blue.erb" }
|
47
|
+
end
|
48
|
+
}.should raise_error(ArgumentError)
|
49
|
+
end
|
50
|
+
|
51
|
+
it "explodes if percentages are only set on some of the alternatives" do
|
52
|
+
running {
|
53
|
+
AB.define :home_page, "Red button or blue button" do
|
54
|
+
alternative(:red, :percent => 10) { "red.erb" }
|
55
|
+
alternative(:red) { "blue.erb" }
|
56
|
+
end
|
57
|
+
}.should raise_error(ArgumentError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe ".test" do
|
62
|
+
before do
|
63
|
+
@test = AB.define :home_page, "Red or Blue home page" do
|
64
|
+
alternative(:red) { "red.erb" }
|
65
|
+
alternative(:blue) { "blue.erb" }
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns an alternative value" do
|
70
|
+
value = AB.test(:home_page, 123)
|
71
|
+
["red.erb", "blue.erb"].should include(value)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "increases the views for the alternative" do
|
75
|
+
alternative = @test.get_alternative(123)
|
76
|
+
running {
|
77
|
+
AB.test(:home_page, 123)
|
78
|
+
}.should change { alternative.views }.by(1)
|
79
|
+
end
|
80
|
+
|
81
|
+
it "rescues Redis failures and still returns a value" do
|
82
|
+
AB.redis.should_receive(:zadd).and_raise(Timeout::Error.new("time's up!"))
|
83
|
+
running {
|
84
|
+
value = AB.test(:home_page, 123)
|
85
|
+
["red.erb", "blue.erb"].should include(value)
|
86
|
+
}.should_not raise_error
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
describe ".track" do
|
91
|
+
before do
|
92
|
+
@test = AB.define :home_page, "Red or blue home page" do
|
93
|
+
alternative(:red) { "red.erb" }
|
94
|
+
alternative(:blue) { "blue.erb" }
|
95
|
+
end
|
96
|
+
|
97
|
+
@alternative = @test.get_alternative(123)
|
98
|
+
@alternative.view! 123
|
99
|
+
end
|
100
|
+
|
101
|
+
it "increases the conversions for the alternative" do
|
102
|
+
running {
|
103
|
+
AB.track :home_page, 123
|
104
|
+
}.should change { @alternative.conversions }.by(1)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "rescues Redis timeout failures" do
|
108
|
+
AB.redis.should_receive(:zscore).and_raise(Timeout::Error.new("time's up!"))
|
109
|
+
running {
|
110
|
+
AB.track :home_page, 123
|
111
|
+
}.should_not raise_error
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe AB::Test do
|
4
|
+
describe "#get_alternative" do
|
5
|
+
before do
|
6
|
+
@test = AB::Test.new(:home_page, "Red or blue home page")
|
7
|
+
@red = @test.alternative(:red) { "red.erb" }
|
8
|
+
@blue = @test.alternative(:blue) { "blue.erb" }
|
9
|
+
end
|
10
|
+
|
11
|
+
it "returns the value of an alternative based on the identity (50/50)" do
|
12
|
+
@test.get_alternative(0).should == @red
|
13
|
+
@test.get_alternative(1).should == @blue
|
14
|
+
end
|
15
|
+
|
16
|
+
it "returns the value of an alternative based on the identity (33/33/33)" do
|
17
|
+
@green = @test.alternative(:green) { "green.erb" } # add an additional alternative
|
18
|
+
@test.get_alternative(0).should == @red
|
19
|
+
@test.get_alternative(1).should == @blue
|
20
|
+
@test.get_alternative(2).should == @green
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns the first alternative when identity is nil" do
|
24
|
+
@test.get_alternative(nil).should == @red
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe "#verify_percentages" do
|
29
|
+
before do
|
30
|
+
@test = AB::Test.new(:home_page, "Red or blue home page")
|
31
|
+
end
|
32
|
+
|
33
|
+
it "raises ArgumentError when some alternatives do not have percentages and some do" do
|
34
|
+
@test.alternative(:red, :percent => 10) { "red.erb" }
|
35
|
+
@test.alternative(:blue) { "blue.erb" }
|
36
|
+
running {
|
37
|
+
@test.verify_percentages
|
38
|
+
}.should raise_error(ArgumentError)
|
39
|
+
end
|
40
|
+
|
41
|
+
it "raises ArgumentError when percentages do not add up to 100" do
|
42
|
+
@test.alternative(:red, :percent => 10) { "red.erb" }
|
43
|
+
@test.alternative(:blue, :percent => 10) { "blue.erb" }
|
44
|
+
running {
|
45
|
+
@test.verify_percentages
|
46
|
+
}.should raise_error(ArgumentError)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
describe "#results" do
|
51
|
+
before do
|
52
|
+
@test = AB::Test.new(:home_page, "Red or blue home page")
|
53
|
+
@red = @test.alternative(:red) { "red.erb" }
|
54
|
+
@blue = @test.alternative(:blue) { "blue.erb" }
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns list of alternatives with stats" do
|
58
|
+
@red.view! 123
|
59
|
+
@red.track_conversion! 123
|
60
|
+
@test.results.should == [
|
61
|
+
{ :name => :red, :value => "red.erb", :views => 1, :conversions => 1, :percent => nil },
|
62
|
+
{ :name => :blue, :value => "blue.erb", :views => 0, :conversions => 0, :percent => nil }
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe "#use" do
|
68
|
+
it "always returns the alternative passed in the block" do
|
69
|
+
test = AB::Test.new(:home_page, "Red or blue home page")
|
70
|
+
red = test.alternative(:red) { "red.erb" }
|
71
|
+
blue = test.alternative(:blue) { "blue.erb" }
|
72
|
+
|
73
|
+
test.use :red do
|
74
|
+
test.get_alternative(0).should == red
|
75
|
+
test.get_alternative(1).should == red
|
76
|
+
end
|
77
|
+
|
78
|
+
test.get_alternative(0).should == red
|
79
|
+
test.get_alternative(1).should == blue
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: the-official-groupme-ab-testing-solution
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Dave Yeu and Pat Nakajima
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-04-28 00:00:00 -04:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: redis
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
version: "0"
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
description:
|
36
|
+
email: pat@groupme.com
|
37
|
+
executables: []
|
38
|
+
|
39
|
+
extensions: []
|
40
|
+
|
41
|
+
extra_rdoc_files: []
|
42
|
+
|
43
|
+
files:
|
44
|
+
- Gemfile
|
45
|
+
- Gemfile.lock
|
46
|
+
- README.md
|
47
|
+
- Rakefile
|
48
|
+
- lib/ab.rb
|
49
|
+
- lib/ab/alternative.rb
|
50
|
+
- lib/ab/interface.rb
|
51
|
+
- lib/ab/test.rb
|
52
|
+
- spec/ab/alternative_spec.rb
|
53
|
+
- spec/ab/interface_spec.rb
|
54
|
+
- spec/ab/test_spec.rb
|
55
|
+
- spec/spec_helper.rb
|
56
|
+
has_rdoc: true
|
57
|
+
homepage: http://github.com/groupme/the-official-groupme-ab-testing-solution
|
58
|
+
licenses: []
|
59
|
+
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
none: false
|
67
|
+
requirements:
|
68
|
+
- - ">="
|
69
|
+
- !ruby/object:Gem::Version
|
70
|
+
hash: 3
|
71
|
+
segments:
|
72
|
+
- 0
|
73
|
+
version: "0"
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ">="
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
hash: 3
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project:
|
86
|
+
rubygems_version: 1.6.2
|
87
|
+
signing_key:
|
88
|
+
specification_version: 3
|
89
|
+
summary: The Official GroupMe AB Testing Solution
|
90
|
+
test_files: []
|
91
|
+
|