split 4.0.1 → 4.0.3
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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +8 -3
- data/.rubocop.yml +2 -5
- data/CHANGELOG.md +38 -0
- data/CONTRIBUTING.md +1 -1
- data/Gemfile +1 -1
- data/README.md +11 -3
- data/Rakefile +4 -5
- data/gemfiles/5.2.gemfile +1 -3
- data/gemfiles/6.0.gemfile +1 -3
- data/gemfiles/6.1.gemfile +1 -3
- data/gemfiles/7.0.gemfile +1 -3
- data/lib/split/algorithms/block_randomization.rb +5 -6
- data/lib/split/algorithms/whiplash.rb +16 -18
- data/lib/split/algorithms.rb +14 -0
- data/lib/split/alternative.rb +21 -22
- data/lib/split/cache.rb +0 -1
- data/lib/split/combined_experiments_helper.rb +4 -4
- data/lib/split/configuration.rb +83 -84
- data/lib/split/dashboard/helpers.rb +6 -7
- data/lib/split/dashboard/pagination_helpers.rb +53 -54
- data/lib/split/dashboard/public/style.css +5 -2
- data/lib/split/dashboard/views/_experiment.erb +2 -1
- data/lib/split/dashboard/views/index.erb +19 -4
- data/lib/split/dashboard.rb +29 -23
- data/lib/split/encapsulated_helper.rb +4 -6
- data/lib/split/experiment.rb +93 -88
- data/lib/split/experiment_catalog.rb +6 -5
- data/lib/split/extensions/string.rb +1 -1
- data/lib/split/goals_collection.rb +8 -10
- data/lib/split/helper.rb +20 -20
- data/lib/split/metric.rb +4 -5
- data/lib/split/persistence/cookie_adapter.rb +44 -47
- data/lib/split/persistence/dual_adapter.rb +7 -8
- data/lib/split/persistence/redis_adapter.rb +3 -4
- data/lib/split/persistence/session_adapter.rb +0 -2
- data/lib/split/persistence.rb +4 -4
- data/lib/split/redis_interface.rb +7 -1
- data/lib/split/trial.rb +23 -24
- data/lib/split/user.rb +12 -13
- data/lib/split/version.rb +1 -1
- data/lib/split/zscore.rb +1 -3
- data/lib/split.rb +26 -25
- data/spec/algorithms/block_randomization_spec.rb +6 -5
- data/spec/algorithms/weighted_sample_spec.rb +6 -5
- data/spec/algorithms/whiplash_spec.rb +4 -5
- data/spec/alternative_spec.rb +35 -36
- data/spec/cache_spec.rb +15 -19
- data/spec/combined_experiments_helper_spec.rb +18 -17
- data/spec/configuration_spec.rb +32 -38
- data/spec/dashboard/pagination_helpers_spec.rb +69 -67
- data/spec/dashboard/paginator_spec.rb +10 -9
- data/spec/dashboard_helpers_spec.rb +19 -18
- data/spec/dashboard_spec.rb +79 -35
- data/spec/encapsulated_helper_spec.rb +12 -14
- data/spec/experiment_catalog_spec.rb +14 -13
- data/spec/experiment_spec.rb +132 -123
- data/spec/goals_collection_spec.rb +17 -15
- data/spec/helper_spec.rb +415 -382
- data/spec/metric_spec.rb +14 -14
- data/spec/persistence/cookie_adapter_spec.rb +23 -8
- data/spec/persistence/dual_adapter_spec.rb +71 -71
- data/spec/persistence/redis_adapter_spec.rb +28 -29
- data/spec/persistence/session_adapter_spec.rb +2 -3
- data/spec/persistence_spec.rb +1 -2
- data/spec/redis_interface_spec.rb +26 -14
- data/spec/spec_helper.rb +16 -13
- data/spec/split_spec.rb +11 -11
- data/spec/support/cookies_mock.rb +1 -2
- data/spec/trial_spec.rb +61 -60
- data/spec/user_spec.rb +36 -36
- data/split.gemspec +21 -20
- metadata +25 -14
- data/.rubocop_todo.yml +0 -226
- data/Appraisals +0 -19
- data/gemfiles/5.0.gemfile +0 -9
- data/gemfiles/5.1.gemfile +0 -9
data/spec/experiment_spec.rb
CHANGED
@@ -1,14 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require
|
2
|
+
|
3
|
+
require "spec_helper"
|
4
|
+
require "time"
|
4
5
|
|
5
6
|
describe Split::Experiment do
|
6
|
-
def new_experiment(goals=[])
|
7
|
-
Split::Experiment.new(
|
7
|
+
def new_experiment(goals = [])
|
8
|
+
Split::Experiment.new("link_color", alternatives: ["blue", "red", "green"], goals: goals)
|
8
9
|
end
|
9
10
|
|
10
11
|
def alternative(color)
|
11
|
-
Split::Alternative.new(color,
|
12
|
+
Split::Alternative.new(color, "link_color")
|
12
13
|
end
|
13
14
|
|
14
15
|
let(:experiment) { new_experiment }
|
@@ -17,10 +18,10 @@ describe Split::Experiment do
|
|
17
18
|
let(:green) { alternative("green") }
|
18
19
|
|
19
20
|
context "with an experiment" do
|
20
|
-
let(:experiment) { Split::Experiment.new(
|
21
|
+
let(:experiment) { Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"]) }
|
21
22
|
|
22
23
|
it "should have a name" do
|
23
|
-
expect(experiment.name).to eq(
|
24
|
+
expect(experiment.name).to eq("basket_text")
|
24
25
|
end
|
25
26
|
|
26
27
|
it "should have alternatives" do
|
@@ -28,7 +29,7 @@ describe Split::Experiment do
|
|
28
29
|
end
|
29
30
|
|
30
31
|
it "should have alternatives with correct names" do
|
31
|
-
expect(experiment.alternatives.collect{|a| a.name}).to eq([
|
32
|
+
expect(experiment.alternatives.collect { |a| a.name }).to eq(["Basket", "Cart"])
|
32
33
|
end
|
33
34
|
|
34
35
|
it "should be resettable by default" do
|
@@ -37,7 +38,7 @@ describe Split::Experiment do
|
|
37
38
|
|
38
39
|
it "should save to redis" do
|
39
40
|
experiment.save
|
40
|
-
expect(Split.redis.exists?(
|
41
|
+
expect(Split.redis.exists?("basket_text")).to be true
|
41
42
|
end
|
42
43
|
|
43
44
|
it "should save the start time to redis" do
|
@@ -45,14 +46,14 @@ describe Split::Experiment do
|
|
45
46
|
expect(Time).to receive(:now).and_return(experiment_start_time)
|
46
47
|
experiment.save
|
47
48
|
|
48
|
-
expect(Split::ExperimentCatalog.find(
|
49
|
+
expect(Split::ExperimentCatalog.find("basket_text").start_time).to eq(experiment_start_time)
|
49
50
|
end
|
50
51
|
|
51
52
|
it "should not save the start time to redis when start_manually is enabled" do
|
52
53
|
expect(Split.configuration).to receive(:start_manually).and_return(true)
|
53
54
|
experiment.save
|
54
55
|
|
55
|
-
expect(Split::ExperimentCatalog.find(
|
56
|
+
expect(Split::ExperimentCatalog.find("basket_text").start_time).to be_nil
|
56
57
|
end
|
57
58
|
|
58
59
|
it "should save the selected algorithm to redis" do
|
@@ -60,16 +61,16 @@ describe Split::Experiment do
|
|
60
61
|
experiment.algorithm = experiment_algorithm
|
61
62
|
experiment.save
|
62
63
|
|
63
|
-
expect(Split::ExperimentCatalog.find(
|
64
|
+
expect(Split::ExperimentCatalog.find("basket_text").algorithm).to eq(experiment_algorithm)
|
64
65
|
end
|
65
66
|
|
66
67
|
it "should handle having a start time stored as a string" do
|
67
68
|
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
68
69
|
expect(Time).to receive(:now).twice.and_return(experiment_start_time)
|
69
70
|
experiment.save
|
70
|
-
Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time)
|
71
|
+
Split.redis.hset(:experiment_start_times, experiment.name, experiment_start_time.to_s)
|
71
72
|
|
72
|
-
expect(Split::ExperimentCatalog.find(
|
73
|
+
expect(Split::ExperimentCatalog.find("basket_text").start_time).to eq(experiment_start_time)
|
73
74
|
end
|
74
75
|
|
75
76
|
it "should handle not having a start time" do
|
@@ -79,17 +80,17 @@ describe Split::Experiment do
|
|
79
80
|
|
80
81
|
Split.redis.hdel(:experiment_start_times, experiment.name)
|
81
82
|
|
82
|
-
expect(Split::ExperimentCatalog.find(
|
83
|
+
expect(Split::ExperimentCatalog.find("basket_text").start_time).to be_nil
|
83
84
|
end
|
84
85
|
|
85
86
|
it "should not create duplicates when saving multiple times" do
|
86
87
|
experiment.save
|
87
88
|
experiment.save
|
88
|
-
expect(Split.redis.exists?(
|
89
|
-
expect(Split.redis.lrange(
|
89
|
+
expect(Split.redis.exists?("basket_text")).to be true
|
90
|
+
expect(Split.redis.lrange("basket_text", 0, -1)).to eq(['{"Basket":1}', '{"Cart":1}'])
|
90
91
|
end
|
91
92
|
|
92
|
-
describe
|
93
|
+
describe "new record?" do
|
93
94
|
it "should know if it hasn't been saved yet" do
|
94
95
|
expect(experiment.new_record?).to be_truthy
|
95
96
|
end
|
@@ -100,58 +101,56 @@ describe Split::Experiment do
|
|
100
101
|
end
|
101
102
|
end
|
102
103
|
|
103
|
-
describe
|
104
|
-
it
|
104
|
+
describe "control" do
|
105
|
+
it "should be the first alternative" do
|
105
106
|
experiment.save
|
106
|
-
expect(experiment.control.name).to eq(
|
107
|
+
expect(experiment.control.name).to eq("Basket")
|
107
108
|
end
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
describe
|
112
|
+
describe "initialization" do
|
112
113
|
it "should set the algorithm when passed as an option to the initializer" do
|
113
|
-
|
114
|
-
|
114
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash)
|
115
|
+
expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash)
|
115
116
|
end
|
116
117
|
|
117
118
|
it "should be possible to make an experiment not resettable" do
|
118
|
-
experiment = Split::Experiment.new(
|
119
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], resettable: false)
|
119
120
|
expect(experiment.resettable).to be_falsey
|
120
121
|
end
|
121
122
|
|
122
|
-
context
|
123
|
+
context "from configuration" do
|
123
124
|
let(:experiment_name) { :my_experiment }
|
124
125
|
let(:experiments) do
|
125
126
|
{
|
126
127
|
experiment_name => {
|
127
|
-
:
|
128
|
+
alternatives: ["Control Opt", "Alt one"]
|
128
129
|
}
|
129
130
|
}
|
130
131
|
end
|
131
132
|
|
132
133
|
before { Split.configuration.experiments = experiments }
|
133
134
|
|
134
|
-
it
|
135
|
+
it "assigns default values to the experiment" do
|
135
136
|
expect(Split::Experiment.new(experiment_name).resettable).to eq(true)
|
136
137
|
end
|
137
138
|
end
|
138
139
|
end
|
139
140
|
|
140
|
-
describe
|
141
|
-
|
141
|
+
describe "persistent configuration" do
|
142
142
|
it "should persist resettable in redis" do
|
143
|
-
experiment = Split::Experiment.new(
|
143
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], resettable: false)
|
144
144
|
experiment.save
|
145
145
|
|
146
|
-
e = Split::ExperimentCatalog.find(
|
146
|
+
e = Split::ExperimentCatalog.find("basket_text")
|
147
147
|
expect(e).to eq(experiment)
|
148
148
|
expect(e.resettable).to be_falsey
|
149
|
-
|
150
149
|
end
|
151
150
|
|
152
|
-
describe
|
153
|
-
let(:experiment) { Split::Experiment.new(
|
154
|
-
let(:meta) { { a:
|
151
|
+
describe "#metadata" do
|
152
|
+
let(:experiment) { Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash, metadata: meta) }
|
153
|
+
let(:meta) { { a: "b" } }
|
155
154
|
|
156
155
|
before do
|
157
156
|
experiment.save
|
@@ -164,20 +163,20 @@ describe Split::Experiment do
|
|
164
163
|
expect(Split.redis.exists?(experiment.metadata_key)).to be_falsey
|
165
164
|
end
|
166
165
|
|
167
|
-
context
|
168
|
-
let(:meta) { {
|
166
|
+
context "simple hash" do
|
167
|
+
let(:meta) { { "basket" => "a", "cart" => "b" } }
|
169
168
|
|
170
169
|
it "should persist metadata in redis" do
|
171
|
-
e = Split::ExperimentCatalog.find(
|
170
|
+
e = Split::ExperimentCatalog.find("basket_text")
|
172
171
|
expect(e).to eq(experiment)
|
173
172
|
expect(e.metadata).to eq(meta)
|
174
173
|
end
|
175
174
|
end
|
176
175
|
|
177
|
-
context
|
178
|
-
let(:meta) { {
|
176
|
+
context "nested hash" do
|
177
|
+
let(:meta) { { "basket" => { "one" => "two" }, "cart" => "b" } }
|
179
178
|
it "should persist metadata in redis" do
|
180
|
-
e = Split::ExperimentCatalog.find(
|
179
|
+
e = Split::ExperimentCatalog.find("basket_text")
|
181
180
|
expect(e).to eq(experiment)
|
182
181
|
expect(e.metadata).to eq(meta)
|
183
182
|
end
|
@@ -185,32 +184,32 @@ describe Split::Experiment do
|
|
185
184
|
end
|
186
185
|
|
187
186
|
it "should persist algorithm in redis" do
|
188
|
-
experiment = Split::Experiment.new(
|
187
|
+
experiment = Split::Experiment.new("basket_text", alternatives: ["Basket", "Cart"], algorithm: Split::Algorithms::Whiplash)
|
189
188
|
experiment.save
|
190
189
|
|
191
|
-
e = Split::ExperimentCatalog.find(
|
190
|
+
e = Split::ExperimentCatalog.find("basket_text")
|
192
191
|
expect(e).to eq(experiment)
|
193
192
|
expect(e.algorithm).to eq(Split::Algorithms::Whiplash)
|
194
193
|
end
|
195
194
|
|
196
195
|
it "should persist a new experiment in redis, that does not exist in the configuration file" do
|
197
|
-
experiment = Split::Experiment.new(
|
196
|
+
experiment = Split::Experiment.new("foobar", alternatives: ["tra", "la"], algorithm: Split::Algorithms::Whiplash)
|
198
197
|
experiment.save
|
199
198
|
|
200
|
-
e = Split::ExperimentCatalog.find(
|
199
|
+
e = Split::ExperimentCatalog.find("foobar")
|
201
200
|
expect(e).to eq(experiment)
|
202
|
-
expect(e.alternatives.collect{|a| a.name}).to eq([
|
201
|
+
expect(e.alternatives.collect { |a| a.name }).to eq(["tra", "la"])
|
203
202
|
end
|
204
203
|
end
|
205
204
|
|
206
|
-
describe
|
207
|
-
it
|
208
|
-
experiment = Split::Experiment.new(
|
205
|
+
describe "deleting" do
|
206
|
+
it "should delete itself" do
|
207
|
+
experiment = Split::Experiment.new("basket_text", alternatives: [ "Basket", "Cart"])
|
209
208
|
experiment.save
|
210
209
|
|
211
210
|
experiment.delete
|
212
|
-
expect(Split.redis.exists?(
|
213
|
-
expect(Split::ExperimentCatalog.find(
|
211
|
+
expect(Split.redis.exists?("link_color")).to be false
|
212
|
+
expect(Split::ExperimentCatalog.find("link_color")).to be_nil
|
214
213
|
end
|
215
214
|
|
216
215
|
it "should increment the version" do
|
@@ -229,7 +228,7 @@ describe Split::Experiment do
|
|
229
228
|
experiment.delete
|
230
229
|
end
|
231
230
|
|
232
|
-
it
|
231
|
+
it "should reset the start time if the experiment should be manually started" do
|
233
232
|
Split.configuration.start_manually = true
|
234
233
|
experiment.start
|
235
234
|
experiment.delete
|
@@ -244,75 +243,75 @@ describe Split::Experiment do
|
|
244
243
|
end
|
245
244
|
end
|
246
245
|
|
247
|
-
describe
|
246
|
+
describe "winner" do
|
248
247
|
it "should have no winner initially" do
|
249
248
|
expect(experiment.winner).to be_nil
|
250
249
|
end
|
251
250
|
end
|
252
251
|
|
253
|
-
describe
|
254
|
-
it
|
252
|
+
describe "winner=" do
|
253
|
+
it "should allow you to specify a winner" do
|
255
254
|
experiment.save
|
256
|
-
experiment.winner =
|
257
|
-
expect(experiment.winner.name).to eq(
|
255
|
+
experiment.winner = "red"
|
256
|
+
expect(experiment.winner.name).to eq("red")
|
258
257
|
end
|
259
258
|
|
260
|
-
it
|
259
|
+
it "should call the on_experiment_winner_choose hook" do
|
261
260
|
expect(Split.configuration.on_experiment_winner_choose).to receive(:call)
|
262
|
-
experiment.winner =
|
261
|
+
experiment.winner = "green"
|
263
262
|
end
|
264
263
|
|
265
|
-
context
|
264
|
+
context "when has_winner state is memoized" do
|
266
265
|
before { expect(experiment).to_not have_winner }
|
267
266
|
|
268
|
-
it
|
269
|
-
experiment.winner =
|
267
|
+
it "should keep has_winner state consistent" do
|
268
|
+
experiment.winner = "red"
|
270
269
|
expect(experiment).to have_winner
|
271
270
|
end
|
272
271
|
end
|
273
272
|
end
|
274
273
|
|
275
|
-
describe
|
276
|
-
before { experiment.winner =
|
274
|
+
describe "reset_winner" do
|
275
|
+
before { experiment.winner = "green" }
|
277
276
|
|
278
|
-
it
|
277
|
+
it "should reset the winner" do
|
279
278
|
experiment.reset_winner
|
280
279
|
expect(experiment.winner).to be_nil
|
281
280
|
end
|
282
281
|
|
283
|
-
context
|
282
|
+
context "when has_winner state is memoized" do
|
284
283
|
before { expect(experiment).to have_winner }
|
285
284
|
|
286
|
-
it
|
285
|
+
it "should keep has_winner state consistent" do
|
287
286
|
experiment.reset_winner
|
288
287
|
expect(experiment).to_not have_winner
|
289
288
|
end
|
290
289
|
end
|
291
290
|
end
|
292
291
|
|
293
|
-
describe
|
294
|
-
context
|
295
|
-
before { experiment.winner =
|
292
|
+
describe "has_winner?" do
|
293
|
+
context "with winner" do
|
294
|
+
before { experiment.winner = "red" }
|
296
295
|
|
297
|
-
it
|
296
|
+
it "returns true" do
|
298
297
|
expect(experiment).to have_winner
|
299
298
|
end
|
300
299
|
end
|
301
300
|
|
302
|
-
context
|
303
|
-
it
|
301
|
+
context "without winner" do
|
302
|
+
it "returns false" do
|
304
303
|
expect(experiment).to_not have_winner
|
305
304
|
end
|
306
305
|
end
|
307
306
|
|
308
|
-
it
|
307
|
+
it "memoizes has_winner state" do
|
309
308
|
expect(experiment).to receive(:winner).once
|
310
309
|
expect(experiment).to_not have_winner
|
311
310
|
expect(experiment).to_not have_winner
|
312
311
|
end
|
313
312
|
end
|
314
313
|
|
315
|
-
describe
|
314
|
+
describe "reset" do
|
316
315
|
let(:reset_manually) { false }
|
317
316
|
|
318
317
|
before do
|
@@ -322,10 +321,10 @@ describe Split::Experiment do
|
|
322
321
|
green.increment_participation
|
323
322
|
end
|
324
323
|
|
325
|
-
it
|
326
|
-
experiment.winner =
|
324
|
+
it "should reset all alternatives" do
|
325
|
+
experiment.winner = "green"
|
327
326
|
|
328
|
-
expect(experiment.next_alternative.name).to eq(
|
327
|
+
expect(experiment.next_alternative.name).to eq("green")
|
329
328
|
green.increment_participation
|
330
329
|
|
331
330
|
experiment.reset
|
@@ -334,10 +333,10 @@ describe Split::Experiment do
|
|
334
333
|
expect(green.completed_count).to eq(0)
|
335
334
|
end
|
336
335
|
|
337
|
-
it
|
338
|
-
experiment.winner =
|
336
|
+
it "should reset the winner" do
|
337
|
+
experiment.winner = "green"
|
339
338
|
|
340
|
-
expect(experiment.next_alternative.name).to eq(
|
339
|
+
expect(experiment.next_alternative.name).to eq("green")
|
341
340
|
green.increment_participation
|
342
341
|
|
343
342
|
experiment.reset
|
@@ -362,50 +361,50 @@ describe Split::Experiment do
|
|
362
361
|
end
|
363
362
|
end
|
364
363
|
|
365
|
-
describe
|
366
|
-
let(:experiment) { Split::ExperimentCatalog.find_or_create(
|
364
|
+
describe "algorithm" do
|
365
|
+
let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue", "red", "green") }
|
367
366
|
|
368
|
-
it
|
367
|
+
it "should use the default algorithm if none is specified" do
|
369
368
|
expect(experiment.algorithm).to eq(Split.configuration.algorithm)
|
370
369
|
end
|
371
370
|
|
372
|
-
it
|
371
|
+
it "should use the user specified algorithm for this experiment if specified" do
|
373
372
|
experiment.algorithm = Split::Algorithms::Whiplash
|
374
373
|
expect(experiment.algorithm).to eq(Split::Algorithms::Whiplash)
|
375
374
|
end
|
376
375
|
end
|
377
376
|
|
378
|
-
describe
|
379
|
-
context
|
380
|
-
let(:experiment) { Split::ExperimentCatalog.find_or_create(
|
377
|
+
describe "#next_alternative" do
|
378
|
+
context "with multiple alternatives" do
|
379
|
+
let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue", "red", "green") }
|
381
380
|
|
382
|
-
context
|
381
|
+
context "with winner" do
|
383
382
|
it "should always return the winner" do
|
384
|
-
green = Split::Alternative.new(
|
385
|
-
experiment.winner =
|
383
|
+
green = Split::Alternative.new("green", "link_color")
|
384
|
+
experiment.winner = "green"
|
386
385
|
|
387
|
-
expect(experiment.next_alternative.name).to eq(
|
386
|
+
expect(experiment.next_alternative.name).to eq("green")
|
388
387
|
green.increment_participation
|
389
388
|
|
390
|
-
expect(experiment.next_alternative.name).to eq(
|
389
|
+
expect(experiment.next_alternative.name).to eq("green")
|
391
390
|
end
|
392
391
|
end
|
393
392
|
|
394
|
-
context
|
393
|
+
context "without winner" do
|
395
394
|
it "should use the specified algorithm" do
|
396
395
|
experiment.algorithm = Split::Algorithms::Whiplash
|
397
|
-
expect(experiment.algorithm).to receive(:choose_alternative).and_return(Split::Alternative.new(
|
398
|
-
expect(experiment.next_alternative.name).to eq(
|
396
|
+
expect(experiment.algorithm).to receive(:choose_alternative).and_return(Split::Alternative.new("green", "link_color"))
|
397
|
+
expect(experiment.next_alternative.name).to eq("green")
|
399
398
|
end
|
400
399
|
end
|
401
400
|
end
|
402
401
|
|
403
|
-
context
|
404
|
-
let(:experiment) { Split::ExperimentCatalog.find_or_create(
|
402
|
+
context "with single alternative" do
|
403
|
+
let(:experiment) { Split::ExperimentCatalog.find_or_create("link_color", "blue") }
|
405
404
|
|
406
405
|
it "should always return the only alternative" do
|
407
|
-
expect(experiment.next_alternative.name).to eq(
|
408
|
-
expect(experiment.next_alternative.name).to eq(
|
406
|
+
expect(experiment.next_alternative.name).to eq("blue")
|
407
|
+
expect(experiment.next_alternative.name).to eq("blue")
|
409
408
|
end
|
410
409
|
end
|
411
410
|
end
|
@@ -426,16 +425,16 @@ describe Split::Experiment do
|
|
426
425
|
end
|
427
426
|
end
|
428
427
|
|
429
|
-
describe
|
428
|
+
describe "changing an existing experiment" do
|
430
429
|
def same_but_different_alternative
|
431
|
-
Split::ExperimentCatalog.find_or_create(
|
430
|
+
Split::ExperimentCatalog.find_or_create("link_color", "blue", "yellow", "orange")
|
432
431
|
end
|
433
432
|
|
434
433
|
it "should reset an experiment if it is loaded with different alternatives" do
|
435
434
|
experiment.save
|
436
435
|
blue.participant_count = 5
|
437
436
|
same_experiment = same_but_different_alternative
|
438
|
-
expect(same_experiment.alternatives.map(&:name)).to eq([
|
437
|
+
expect(same_experiment.alternatives.map(&:name)).to eq(["blue", "yellow", "orange"])
|
439
438
|
expect(blue.participant_count).to eq(0)
|
440
439
|
end
|
441
440
|
|
@@ -451,7 +450,7 @@ describe Split::Experiment do
|
|
451
450
|
context "when metadata is changed" do
|
452
451
|
it "should increase version" do
|
453
452
|
experiment.save
|
454
|
-
experiment.metadata = {
|
453
|
+
experiment.metadata = { "foo" => "bar" }
|
455
454
|
|
456
455
|
expect { experiment.save }.to change { experiment.version }.by(1)
|
457
456
|
end
|
@@ -463,7 +462,7 @@ describe Split::Experiment do
|
|
463
462
|
end
|
464
463
|
end
|
465
464
|
|
466
|
-
context
|
465
|
+
context "when experiment configuration is changed" do
|
467
466
|
let(:reset_manually) { false }
|
468
467
|
|
469
468
|
before do
|
@@ -476,15 +475,15 @@ describe Split::Experiment do
|
|
476
475
|
experiment.save
|
477
476
|
end
|
478
477
|
|
479
|
-
it
|
478
|
+
it "resets all alternatives" do
|
480
479
|
expect(green.participant_count).to eq(0)
|
481
480
|
expect(green.completed_count).to eq(0)
|
482
481
|
end
|
483
482
|
|
484
|
-
context
|
483
|
+
context "when reset_manually is set" do
|
485
484
|
let(:reset_manually) { true }
|
486
485
|
|
487
|
-
it
|
486
|
+
it "does not reset alternatives" do
|
488
487
|
expect(green.participant_count).to eq(2)
|
489
488
|
expect(green.completed_count).to eq(0)
|
490
489
|
end
|
@@ -492,16 +491,16 @@ describe Split::Experiment do
|
|
492
491
|
end
|
493
492
|
end
|
494
493
|
|
495
|
-
describe
|
494
|
+
describe "alternatives passed as non-strings" do
|
496
495
|
it "should throw an exception if an alternative is passed that is not a string" do
|
497
|
-
expect
|
498
|
-
expect
|
496
|
+
expect { Split::ExperimentCatalog.find_or_create("link_color", :blue, :red) }.to raise_error(ArgumentError)
|
497
|
+
expect { Split::ExperimentCatalog.find_or_create("link_enabled", true, false) }.to raise_error(ArgumentError)
|
499
498
|
end
|
500
499
|
end
|
501
500
|
|
502
|
-
describe
|
501
|
+
describe "specifying weights" do
|
503
502
|
let(:experiment_with_weight) {
|
504
|
-
Split::ExperimentCatalog.find_or_create(
|
503
|
+
Split::ExperimentCatalog.find_or_create("link_color", { "blue" => 1 }, { "red" => 2 })
|
505
504
|
}
|
506
505
|
|
507
506
|
it "should work for a new experiment" do
|
@@ -520,7 +519,7 @@ describe Split::Experiment do
|
|
520
519
|
}
|
521
520
|
|
522
521
|
context "saving experiment" do
|
523
|
-
let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({
|
522
|
+
let(:same_but_different_goals) { Split::ExperimentCatalog.find_or_create({ "link_color" => ["purchase", "refund"] }, "blue", "red", "green") }
|
524
523
|
|
525
524
|
before { experiment.save }
|
526
525
|
|
@@ -532,7 +531,6 @@ describe Split::Experiment do
|
|
532
531
|
same_but_different_goals
|
533
532
|
expect(Split::ExperimentCatalog.find("link_color").goals).to eq(["purchase", "refund"])
|
534
533
|
end
|
535
|
-
|
536
534
|
end
|
537
535
|
|
538
536
|
it "should have goals" do
|
@@ -541,9 +539,9 @@ describe Split::Experiment do
|
|
541
539
|
|
542
540
|
context "find or create experiment" do
|
543
541
|
it "should have correct goals" do
|
544
|
-
experiment = Split::ExperimentCatalog.find_or_create({
|
542
|
+
experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green")
|
545
543
|
expect(experiment.goals).to eq(["purchase", "refund"])
|
546
|
-
experiment = Split::ExperimentCatalog.find_or_create(
|
544
|
+
experiment = Split::ExperimentCatalog.find_or_create("link_color3", "blue", "red", "green")
|
547
545
|
expect(experiment.goals).to eq([])
|
548
546
|
end
|
549
547
|
end
|
@@ -551,19 +549,19 @@ describe Split::Experiment do
|
|
551
549
|
|
552
550
|
describe "beta probability calculation" do
|
553
551
|
it "should return a hash with the probability of each alternative being the best" do
|
554
|
-
experiment = Split::ExperimentCatalog.find_or_create(
|
552
|
+
experiment = Split::ExperimentCatalog.find_or_create("mathematicians", "bernoulli", "poisson", "lagrange")
|
555
553
|
experiment.calc_winning_alternatives
|
556
554
|
expect(experiment.alternative_probabilities).not_to be_nil
|
557
555
|
end
|
558
556
|
|
559
557
|
it "should return between 46% and 54% probability for an experiment with 2 alternatives and no data" do
|
560
|
-
experiment = Split::ExperimentCatalog.find_or_create(
|
558
|
+
experiment = Split::ExperimentCatalog.find_or_create("scientists", "einstein", "bohr")
|
561
559
|
experiment.calc_winning_alternatives
|
562
560
|
expect(experiment.alternatives[0].p_winner).to be_within(0.04).of(0.50)
|
563
561
|
end
|
564
562
|
|
565
|
-
it "should calculate the probability of being the winning alternative separately for each goal", :
|
566
|
-
experiment = Split::ExperimentCatalog.find_or_create({
|
563
|
+
it "should calculate the probability of being the winning alternative separately for each goal", skip: true do
|
564
|
+
experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green")
|
567
565
|
goal1 = experiment.goals[0]
|
568
566
|
goal2 = experiment.goals[1]
|
569
567
|
experiment.alternatives.each do |alternative|
|
@@ -578,8 +576,19 @@ describe Split::Experiment do
|
|
578
576
|
expect(p_goal1).not_to be_within(0.04).of(p_goal2)
|
579
577
|
end
|
580
578
|
|
579
|
+
it "should not calculate when data is not valid for beta distribution" do
|
580
|
+
experiment = Split::ExperimentCatalog.find_or_create("scientists", "einstein", "bohr")
|
581
|
+
|
582
|
+
experiment.alternatives.each do |alternative|
|
583
|
+
alternative.participant_count = 9
|
584
|
+
alternative.set_completed_count(10)
|
585
|
+
end
|
586
|
+
|
587
|
+
expect { experiment.calc_winning_alternatives }.to_not raise_error
|
588
|
+
end
|
589
|
+
|
581
590
|
it "should return nil and not re-calculate probabilities if they have already been calculated today" do
|
582
|
-
experiment = Split::ExperimentCatalog.find_or_create({
|
591
|
+
experiment = Split::ExperimentCatalog.find_or_create({ "link_color3" => ["purchase", "refund"] }, "blue", "red", "green")
|
583
592
|
expect(experiment.calc_winning_alternatives).not_to be nil
|
584
593
|
expect(experiment.calc_winning_alternatives).to be nil
|
585
594
|
end
|