the-official-groupme-ab-testing-solution 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
|