split 0.5.0 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -27,9 +27,25 @@ describe Split::Configuration do
27
27
  end
28
28
 
29
29
  it "should provide a default pattern for robots" do
30
- %w[Baidu Gigabot Googlebot libwww-perl lwp-trivial msnbot SiteUptime Slurp WordPress ZIBB ZyBorg].each do |robot|
30
+ %w[Baidu Gigabot Googlebot libwww-perl lwp-trivial msnbot SiteUptime Slurp WordPress ZIBB ZyBorg YandexBot AdsBot-Google Wget curl bitlybot facebookexternalhit spider].each do |robot|
31
31
  @config.robot_regex.should =~ robot
32
32
  end
33
+
34
+ @config.robot_regex.should =~ "EventMachine HttpClient"
35
+ @config.robot_regex.should =~ "libwww-perl/5.836"
36
+ @config.robot_regex.should =~ "Pingdom.com_bot_version_1.4_(http://www.pingdom.com)"
37
+
38
+ @config.robot_regex.should =~ " - "
39
+ end
40
+
41
+ it "should accept real UAs with the robot regexp" do
42
+ @config.robot_regex.should_not =~ "Mozilla/5.0 (Windows; U; Windows NT 6.0; en-US; rv:1.9.1.4) Gecko/20091017 SeaMonkey/2.0"
43
+ @config.robot_regex.should_not =~ "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; F-6.0SP2-20041109; .NET CLR 2.0.50727; .NET CLR 3.0.04506.648; .NET CLR 3.5.21022; .NET CLR 1.1.4322; InfoPath.3)"
44
+ end
45
+
46
+ it "should allow adding a bot to the bot list" do
47
+ @config.bots["newbot"] = "An amazing test bot"
48
+ @config.robot_regex.should =~ "newbot"
33
49
  end
34
50
 
35
51
  it "should use the session adapter for persistence by default" do
@@ -43,13 +59,85 @@ describe Split::Configuration do
43
59
  @config.metrics.should_not be_nil
44
60
  @config.metrics.keys.should == [:my_metric]
45
61
  end
46
-
62
+
47
63
  it "should allow loading of experiment using experment_for" do
48
64
  @config.experiments = {:my_experiment=>
49
65
  {:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}}
50
66
  @config.experiment_for(:my_experiment).should == {:alternatives=>["control_opt", ["other_opt"]]}
51
67
  end
52
-
68
+
69
+ context "when experiments are defined via YAML" do
70
+ context "as strings" do
71
+ context "in a basic configuration" do
72
+ before do
73
+ experiments_yaml = <<-eos
74
+ my_experiment:
75
+ alternatives:
76
+ - Control Opt
77
+ - Alt One
78
+ - Alt Two
79
+ resettable: false
80
+ eos
81
+ @config.experiments = YAML.load(experiments_yaml)
82
+ end
83
+
84
+ it 'should normalize experiments' do
85
+ @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
86
+ end
87
+ end
88
+
89
+ context "in a complex configuration" do
90
+ before do
91
+ experiments_yaml = <<-eos
92
+ my_experiment:
93
+ alternatives:
94
+ - name: Control Opt
95
+ percent: 67
96
+ - name: Alt One
97
+ percent: 10
98
+ - name: Alt Two
99
+ percent: 23
100
+ resettable: false
101
+ metric: my_metric
102
+ another_experiment:
103
+ alternatives:
104
+ - a
105
+ - b
106
+ eos
107
+ @config.experiments = YAML.load(experiments_yaml)
108
+ end
109
+
110
+ it "should normalize experiments" do
111
+ @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>[{"Control Opt"=>0.67},
112
+ [{"Alt One"=>0.1}, {"Alt Two"=>0.23}]]}, :another_experiment=>{:alternatives=>["a", ["b"]]}}
113
+ end
114
+
115
+ it "should recognize metrics" do
116
+ @config.metrics.should_not be_nil
117
+ @config.metrics.keys.should == [:my_metric]
118
+ end
119
+ end
120
+ end
121
+
122
+ context "as symbols" do
123
+ before do
124
+ experiments_yaml = <<-eos
125
+ :my_experiment:
126
+ :alternatives:
127
+ - Control Opt
128
+ - Alt One
129
+ - Alt Two
130
+ :resettable: false
131
+ eos
132
+ @config.experiments = YAML.load(experiments_yaml)
133
+ end
134
+
135
+ it "should normalize experiments" do
136
+ @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>["Control Opt", ["Alt One", "Alt Two"]]}}
137
+ end
138
+ end
139
+ end
140
+
53
141
  it "should normalize experiments" do
54
142
  @config.experiments = {
55
143
  :my_experiment => {
@@ -60,7 +148,7 @@ describe Split::Configuration do
60
148
  ],
61
149
  }
62
150
  }
63
-
151
+
64
152
  @config.normalized_experiments.should == {:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}
65
153
  end
66
154
  end
@@ -5,6 +5,22 @@ require 'split/dashboard'
5
5
  describe Split::Dashboard do
6
6
  include Rack::Test::Methods
7
7
 
8
+ let(:link_color) {
9
+ Split::Experiment.find_or_create('link_color', 'blue', 'red')
10
+ }
11
+
12
+ def link(color)
13
+ Split::Alternative.new(color, 'link_color')
14
+ end
15
+
16
+ let(:red_link) {
17
+ link("red")
18
+ }
19
+
20
+ let(:blue_link) {
21
+ link("blue")
22
+ }
23
+
8
24
  def app
9
25
  @app ||= Split::Dashboard
10
26
  end
@@ -15,33 +31,33 @@ describe Split::Dashboard do
15
31
  end
16
32
 
17
33
  it "should reset an experiment" do
18
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
34
+ experiment = link_color
19
35
 
20
- red = Split::Alternative.new('red', 'link_color')
21
- blue = Split::Alternative.new('blue', 'link_color')
22
- red.participant_count = 5
23
- blue.participant_count = 6
36
+ red_link.participant_count = 5
37
+ blue_link.participant_count = 7
38
+ experiment.winner = 'blue'
24
39
 
25
40
  post '/reset/link_color'
26
41
 
27
42
  last_response.should be_redirect
28
43
 
29
- new_red_count = Split::Alternative.new('red', 'link_color').participant_count
30
- new_blue_count = Split::Alternative.new('blue', 'link_color').participant_count
44
+ new_red_count = red_link.participant_count
45
+ new_blue_count = blue_link.participant_count
31
46
 
32
47
  new_blue_count.should eql(0)
33
48
  new_red_count.should eql(0)
49
+ experiment.winner.should be_nil
34
50
  end
35
51
 
36
52
  it "should delete an experiment" do
37
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
53
+ experiment = link_color
38
54
  delete '/link_color'
39
55
  last_response.should be_redirect
40
56
  Split::Experiment.find('link_color').should be_nil
41
57
  end
42
58
 
43
59
  it "should mark an alternative as the winner" do
44
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
60
+ experiment = link_color
45
61
  experiment.winner.should be_nil
46
62
 
47
63
  post '/link_color', :alternative => 'red'
@@ -53,7 +69,7 @@ describe Split::Dashboard do
53
69
  it "should display the start date" do
54
70
  experiment_start_time = Time.parse('2011-07-07')
55
71
  Time.stub(:now => experiment_start_time)
56
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
72
+ experiment = link_color
57
73
 
58
74
  get '/'
59
75
 
@@ -63,7 +79,7 @@ describe Split::Dashboard do
63
79
  it "should handle experiments without a start date" do
64
80
  experiment_start_time = Time.parse('2011-07-07')
65
81
  Time.stub(:now => experiment_start_time)
66
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
82
+ experiment = link_color
67
83
 
68
84
  Split.redis.hdel(:experiment_start_times, experiment.name)
69
85
 
@@ -4,9 +4,24 @@ require 'split/algorithms'
4
4
  require 'time'
5
5
 
6
6
  describe Split::Experiment do
7
+ def new_experiment(goals=[])
8
+ Split::Experiment.new('link_color', :alternatives => ['blue', 'red', 'green'], :goals => goals)
9
+ end
10
+
11
+ def alternative(color)
12
+ Split::Alternative.new(color, 'link_color')
13
+ end
14
+
15
+ let(:experiment) {
16
+ new_experiment
17
+ }
18
+
19
+ let(:blue) { alternative("blue") }
20
+ let(:green) { alternative("green") }
21
+
7
22
  context "with an experiment" do
8
- let(:experiment) { Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"]) }
9
-
23
+ let(:experiment) { Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"]) }
24
+
10
25
  it "should have a name" do
11
26
  experiment.name.should eql('basket_text')
12
27
  end
@@ -14,15 +29,15 @@ describe Split::Experiment do
14
29
  it "should have alternatives" do
15
30
  experiment.alternatives.length.should be 2
16
31
  end
17
-
32
+
18
33
  it "should have alternatives with correct names" do
19
34
  experiment.alternatives.collect{|a| a.name}.should == ['Basket', 'Cart']
20
35
  end
21
-
36
+
22
37
  it "should be resettable by default" do
23
38
  experiment.resettable.should be_true
24
39
  end
25
-
40
+
26
41
  it "should save to redis" do
27
42
  experiment.save
28
43
  Split.redis.exists('basket_text').should be true
@@ -35,10 +50,10 @@ describe Split::Experiment do
35
50
 
36
51
  Split::Experiment.find('basket_text').start_time.should == experiment_start_time
37
52
  end
38
-
53
+
39
54
  it "should save the selected algorithm to redis" do
40
55
  experiment_algorithm = Split::Algorithms::Whiplash
41
- experiment.algorithm = experiment_algorithm
56
+ experiment.algorithm = experiment_algorithm
42
57
  experiment.save
43
58
 
44
59
  Split::Experiment.find('basket_text').algorithm.should == experiment_algorithm
@@ -60,7 +75,7 @@ describe Split::Experiment do
60
75
  Split.redis.exists('basket_text').should be true
61
76
  Split.redis.lrange('basket_text', 0, -1).should eql(['Basket', "Cart"])
62
77
  end
63
-
78
+
64
79
  describe 'new record?' do
65
80
  it "should know if it hasn't been saved yet" do
66
81
  experiment.new_record?.should be_true
@@ -71,18 +86,19 @@ describe Split::Experiment do
71
86
  experiment.new_record?.should be_false
72
87
  end
73
88
  end
74
-
89
+
75
90
  describe 'find' do
76
91
  it "should return an existing experiment" do
77
92
  experiment.save
78
- Split::Experiment.find('basket_text').name.should eql('basket_text')
93
+ experiment = Split::Experiment.find('basket_text')
94
+ experiment.name.should eql('basket_text')
79
95
  end
80
96
 
81
97
  it "should return an existing experiment" do
82
98
  Split::Experiment.find('non_existent_experiment').should be_nil
83
99
  end
84
100
  end
85
-
101
+
86
102
  describe 'control' do
87
103
  it 'should be the first alternative' do
88
104
  experiment.save
@@ -90,80 +106,74 @@ describe Split::Experiment do
90
106
  end
91
107
  end
92
108
  end
109
+
93
110
  describe 'initialization' do
94
111
  it "should set the algorithm when passed as an option to the initializer" do
95
- experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash)
112
+ experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash)
96
113
  experiment.algorithm.should == Split::Algorithms::Whiplash
97
114
  end
98
-
115
+
99
116
  it "should be possible to make an experiment not resettable" do
100
- experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :resettable => false)
101
- experiment.resettable.should be_false
117
+ experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false)
118
+ experiment.resettable.should be_false
102
119
  end
103
120
  end
104
-
121
+
105
122
  describe 'persistent configuration' do
106
-
123
+
107
124
  it "should persist resettable in redis" do
108
- experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :resettable => false)
125
+ experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :resettable => false)
109
126
  experiment.save
110
-
127
+
111
128
  e = Split::Experiment.find('basket_text')
112
129
  e.should == experiment
113
130
  e.resettable.should be_false
114
-
131
+
115
132
  end
116
-
133
+
117
134
  it "should persist algorithm in redis" do
118
- experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash)
135
+ experiment = Split::Experiment.new('basket_text', :alternatives => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash)
119
136
  experiment.save
120
-
137
+
121
138
  e = Split::Experiment.find('basket_text')
122
139
  e.should == experiment
123
140
  e.algorithm.should == Split::Algorithms::Whiplash
124
141
  end
125
142
  end
126
143
 
127
-
128
-
129
-
130
144
  describe 'deleting' do
131
145
  it 'should delete itself' do
132
- experiment = Split::Experiment.new('basket_text', :alternative_names => [ 'Basket', "Cart"])
146
+ experiment = Split::Experiment.new('basket_text', :alternatives => [ 'Basket', "Cart"])
133
147
  experiment.save
134
148
 
135
149
  experiment.delete
136
- Split.redis.exists('basket_text').should be false
137
- Split::Experiment.find('basket_text').should be_nil
150
+ Split.redis.exists('link_color').should be false
151
+ Split::Experiment.find('link_color').should be_nil
138
152
  end
139
153
 
140
154
  it "should increment the version" do
141
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
142
155
  experiment.version.should eql(0)
143
156
  experiment.delete
144
157
  experiment.version.should eql(1)
145
158
  end
146
159
  end
147
160
 
161
+
148
162
  describe 'winner' do
149
163
  it "should have no winner initially" do
150
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
151
164
  experiment.winner.should be_nil
152
165
  end
153
166
 
154
167
  it "should allow you to specify a winner" do
155
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
168
+ experiment.save
156
169
  experiment.winner = 'red'
157
-
158
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
159
170
  experiment.winner.name.should == 'red'
160
171
  end
161
172
  end
162
173
 
163
174
  describe 'reset' do
175
+ before { experiment.save }
164
176
  it 'should reset all alternatives' do
165
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
166
- green = Split::Alternative.new('green', 'link_color')
167
177
  experiment.winner = 'green'
168
178
 
169
179
  experiment.next_alternative.name.should eql('green')
@@ -171,14 +181,11 @@ describe Split::Experiment do
171
181
 
172
182
  experiment.reset
173
183
 
174
- reset_green = Split::Alternative.new('green', 'link_color')
175
- reset_green.participant_count.should eql(0)
176
- reset_green.completed_count.should eql(0)
184
+ green.participant_count.should eql(0)
185
+ green.completed_count.should eql(0)
177
186
  end
178
187
 
179
188
  it 'should reset the winner' do
180
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
181
- green = Split::Alternative.new('green', 'link_color')
182
189
  experiment.winner = 'green'
183
190
 
184
191
  experiment.next_alternative.name.should eql('green')
@@ -190,29 +197,28 @@ describe Split::Experiment do
190
197
  end
191
198
 
192
199
  it "should increment the version" do
193
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
194
200
  experiment.version.should eql(0)
195
201
  experiment.reset
196
202
  experiment.version.should eql(1)
197
203
  end
198
204
  end
199
-
205
+
200
206
  describe 'algorithm' do
201
207
  let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green') }
202
-
208
+
203
209
  it 'should use the default algorithm if none is specified' do
204
210
  experiment.algorithm.should == Split.configuration.algorithm
205
211
  end
206
-
212
+
207
213
  it 'should use the user specified algorithm for this experiment if specified' do
208
214
  experiment.algorithm = Split::Algorithms::Whiplash
209
215
  experiment.algorithm.should == Split::Algorithms::Whiplash
210
216
  end
211
217
  end
212
-
218
+
213
219
  describe 'next_alternative' do
214
220
  let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green') }
215
-
221
+
216
222
  it "should always return the winner if one exists" do
217
223
  green = Split::Alternative.new('green', 'link_color')
218
224
  experiment.winner = 'green'
@@ -220,42 +226,42 @@ describe Split::Experiment do
220
226
  experiment.next_alternative.name.should eql('green')
221
227
  green.increment_participation
222
228
 
223
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
224
229
  experiment.next_alternative.name.should eql('green')
225
230
  end
226
-
231
+
227
232
  it "should use the specified algorithm if a winner does not exist" do
228
233
  Split.configuration.algorithm.should_receive(:choose_alternative).and_return(Split::Alternative.new('green', 'link_color'))
229
234
  experiment.next_alternative.name.should eql('green')
230
235
  end
231
236
  end
232
-
237
+
233
238
  describe 'single alternative' do
234
239
  let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue') }
235
-
240
+
236
241
  it "should always return the color blue" do
237
242
  experiment.next_alternative.name.should eql('blue')
238
- end
243
+ end
239
244
  end
240
245
 
241
246
  describe 'changing an existing experiment' do
247
+ def same_but_different_alternative
248
+ Split::Experiment.find_or_create('link_color', 'blue', 'yellow', 'orange')
249
+ end
250
+
242
251
  it "should reset an experiment if it is loaded with different alternatives" do
243
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
244
- blue = Split::Alternative.new('blue', 'link_color')
252
+ experiment.save
245
253
  blue.participant_count = 5
246
- blue.save
247
- same_experiment = Split::Experiment.find_or_create('link_color', 'blue', 'yellow', 'orange')
254
+ same_experiment = same_but_different_alternative
248
255
  same_experiment.alternatives.map(&:name).should eql(['blue', 'yellow', 'orange'])
249
- new_blue = Split::Alternative.new('blue', 'link_color')
250
- new_blue.participant_count.should eql(0)
256
+ blue.participant_count.should eql(0)
251
257
  end
252
258
 
253
259
  it "should only reset once" do
254
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
260
+ experiment.save
255
261
  experiment.version.should eql(0)
256
- same_experiment = Split::Experiment.find_or_create('link_color', 'blue', 'yellow', 'orange')
262
+ same_experiment = same_but_different_alternative
257
263
  same_experiment.version.should eql(1)
258
- same_experiment_again = Split::Experiment.find_or_create('link_color', 'blue', 'yellow', 'orange')
264
+ same_experiment_again = same_but_different_alternative
259
265
  same_experiment_again.version.should eql(1)
260
266
  end
261
267
  end
@@ -268,18 +274,55 @@ describe Split::Experiment do
268
274
  end
269
275
 
270
276
  describe 'specifying weights' do
271
- it "should work for a new experiment" do
272
- experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 2 })
277
+ let(:experiment_with_weight) {
278
+ Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 2 })
279
+ }
273
280
 
274
- experiment.alternatives.map(&:weight).should == [1, 2]
281
+ it "should work for a new experiment" do
282
+ experiment_with_weight.alternatives.map(&:weight).should == [1, 2]
275
283
  end
276
284
 
277
285
  it "should work for an existing experiment" do
278
- experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
279
286
  experiment.save
287
+ experiment_with_weight.alternatives.map(&:weight).should == [1, 2]
288
+ end
289
+ end
280
290
 
281
- same_experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 2 })
282
- same_experiment.alternatives.map(&:weight).should == [1, 2]
291
+ describe "specifying goals" do
292
+ let(:experiment) {
293
+ new_experiment(["purchase"])
294
+ }
295
+
296
+ context "saving experiment" do
297
+ def same_but_different_goals
298
+ Split::Experiment.find_or_create({'link_color' => ["purchase", "refund"]}, 'blue', 'red', 'green')
299
+ end
300
+
301
+ before { experiment.save }
302
+
303
+ it "can find existing experiment" do
304
+ Split::Experiment.find("link_color").name.should eql("link_color")
305
+ end
306
+
307
+ it "should reset an experiment if it is loaded with different goals" do
308
+ same_experiment = same_but_different_goals
309
+ Split::Experiment.find("link_color").goals.should == ["purchase", "refund"]
310
+ end
311
+
312
+ end
313
+
314
+ it "should have goals" do
315
+ experiment.goals.should eql(["purchase"])
316
+ end
317
+
318
+ context "find or create experiment" do
319
+ it "should have correct goals" do
320
+ experiment = Split::Experiment.find_or_create({'link_color3' => ["purchase", "refund"]}, 'blue', 'red', 'green')
321
+ experiment.goals.should == ["purchase", "refund"]
322
+ experiment = Split::Experiment.find_or_create('link_color3', 'blue', 'red', 'green')
323
+ experiment.goals.should == []
324
+ end
283
325
  end
284
326
  end
327
+
285
328
  end