ab-split 1.0.0
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 +7 -0
- data/.codeclimate.yml +30 -0
- data/.csslintrc +2 -0
- data/.eslintignore +1 -0
- data/.eslintrc +213 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/ISSUE_TEMPLATE/bug_report.md +24 -0
- data/.rspec +1 -0
- data/.rubocop.yml +7 -0
- data/.rubocop_todo.yml +679 -0
- data/.travis.yml +60 -0
- data/Appraisals +19 -0
- data/CHANGELOG.md +696 -0
- data/CODE_OF_CONDUCT.md +74 -0
- data/CONTRIBUTING.md +62 -0
- data/Gemfile +7 -0
- data/LICENSE +22 -0
- data/README.md +955 -0
- data/Rakefile +9 -0
- data/ab-split.gemspec +44 -0
- data/gemfiles/4.2.gemfile +9 -0
- data/gemfiles/5.0.gemfile +9 -0
- data/gemfiles/5.1.gemfile +9 -0
- data/gemfiles/5.2.gemfile +9 -0
- data/gemfiles/6.0.gemfile +9 -0
- data/lib/split.rb +76 -0
- data/lib/split/algorithms/block_randomization.rb +23 -0
- data/lib/split/algorithms/weighted_sample.rb +18 -0
- data/lib/split/algorithms/whiplash.rb +38 -0
- data/lib/split/alternative.rb +191 -0
- data/lib/split/combined_experiments_helper.rb +37 -0
- data/lib/split/configuration.rb +255 -0
- data/lib/split/dashboard.rb +74 -0
- data/lib/split/dashboard/helpers.rb +45 -0
- data/lib/split/dashboard/pagination_helpers.rb +86 -0
- data/lib/split/dashboard/paginator.rb +16 -0
- data/lib/split/dashboard/public/dashboard-filtering.js +43 -0
- data/lib/split/dashboard/public/dashboard.js +24 -0
- data/lib/split/dashboard/public/jquery-1.11.1.min.js +4 -0
- data/lib/split/dashboard/public/reset.css +48 -0
- data/lib/split/dashboard/public/style.css +328 -0
- data/lib/split/dashboard/views/_controls.erb +18 -0
- data/lib/split/dashboard/views/_experiment.erb +155 -0
- data/lib/split/dashboard/views/_experiment_with_goal_header.erb +8 -0
- data/lib/split/dashboard/views/index.erb +26 -0
- data/lib/split/dashboard/views/layout.erb +27 -0
- data/lib/split/encapsulated_helper.rb +42 -0
- data/lib/split/engine.rb +15 -0
- data/lib/split/exceptions.rb +6 -0
- data/lib/split/experiment.rb +486 -0
- data/lib/split/experiment_catalog.rb +51 -0
- data/lib/split/extensions/string.rb +16 -0
- data/lib/split/goals_collection.rb +45 -0
- data/lib/split/helper.rb +165 -0
- data/lib/split/metric.rb +101 -0
- data/lib/split/persistence.rb +28 -0
- data/lib/split/persistence/cookie_adapter.rb +94 -0
- data/lib/split/persistence/dual_adapter.rb +85 -0
- data/lib/split/persistence/redis_adapter.rb +57 -0
- data/lib/split/persistence/session_adapter.rb +29 -0
- data/lib/split/redis_interface.rb +50 -0
- data/lib/split/trial.rb +117 -0
- data/lib/split/user.rb +69 -0
- data/lib/split/version.rb +7 -0
- data/lib/split/zscore.rb +57 -0
- data/spec/algorithms/block_randomization_spec.rb +32 -0
- data/spec/algorithms/weighted_sample_spec.rb +19 -0
- data/spec/algorithms/whiplash_spec.rb +24 -0
- data/spec/alternative_spec.rb +320 -0
- data/spec/combined_experiments_helper_spec.rb +57 -0
- data/spec/configuration_spec.rb +258 -0
- data/spec/dashboard/pagination_helpers_spec.rb +200 -0
- data/spec/dashboard/paginator_spec.rb +37 -0
- data/spec/dashboard_helpers_spec.rb +42 -0
- data/spec/dashboard_spec.rb +210 -0
- data/spec/encapsulated_helper_spec.rb +52 -0
- data/spec/experiment_catalog_spec.rb +53 -0
- data/spec/experiment_spec.rb +533 -0
- data/spec/goals_collection_spec.rb +80 -0
- data/spec/helper_spec.rb +1111 -0
- data/spec/metric_spec.rb +31 -0
- data/spec/persistence/cookie_adapter_spec.rb +106 -0
- data/spec/persistence/dual_adapter_spec.rb +194 -0
- data/spec/persistence/redis_adapter_spec.rb +90 -0
- data/spec/persistence/session_adapter_spec.rb +32 -0
- data/spec/persistence_spec.rb +34 -0
- data/spec/redis_interface_spec.rb +111 -0
- data/spec/spec_helper.rb +52 -0
- data/spec/split_spec.rb +43 -0
- data/spec/support/cookies_mock.rb +20 -0
- data/spec/trial_spec.rb +299 -0
- data/spec/user_spec.rb +87 -0
- metadata +322 -0
@@ -0,0 +1,32 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Split::Algorithms::BlockRandomization do
|
4
|
+
|
5
|
+
let(:experiment) { Split::Experiment.new 'experiment' }
|
6
|
+
let(:alternative_A) { Split::Alternative.new 'A', 'experiment' }
|
7
|
+
let(:alternative_B) { Split::Alternative.new 'B', 'experiment' }
|
8
|
+
let(:alternative_C) { Split::Alternative.new 'C', 'experiment' }
|
9
|
+
|
10
|
+
before :each do
|
11
|
+
allow(experiment).to receive(:alternatives) { [alternative_A, alternative_B, alternative_C] }
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return an alternative" do
|
15
|
+
expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment).class).to eq(Split::Alternative)
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should always return the minimum participation option" do
|
19
|
+
allow(alternative_A).to receive(:participant_count) { 1 }
|
20
|
+
allow(alternative_B).to receive(:participant_count) { 1 }
|
21
|
+
allow(alternative_C).to receive(:participant_count) { 0 }
|
22
|
+
expect(Split::Algorithms::BlockRandomization.choose_alternative(experiment)).to eq(alternative_C)
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should return one of the minimum participation options when multiple" do
|
26
|
+
allow(alternative_A).to receive(:participant_count) { 0 }
|
27
|
+
allow(alternative_B).to receive(:participant_count) { 0 }
|
28
|
+
allow(alternative_C).to receive(:participant_count) { 0 }
|
29
|
+
alternative = Split::Algorithms::BlockRandomization.choose_alternative(experiment)
|
30
|
+
expect([alternative_A, alternative_B, alternative_C].include?(alternative)).to be(true)
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Split::Algorithms::WeightedSample do
|
5
|
+
it "should return an alternative" do
|
6
|
+
experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
|
7
|
+
expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).class).to eq(Split::Alternative)
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should always return a heavily weighted option" do
|
11
|
+
experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
|
12
|
+
expect(Split::Algorithms::WeightedSample.choose_alternative(experiment).name).to eq('blue')
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should return one of the results" do
|
16
|
+
experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
17
|
+
expect(['red', 'blue']).to include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require "spec_helper"
|
3
|
+
|
4
|
+
describe Split::Algorithms::Whiplash do
|
5
|
+
|
6
|
+
it "should return an algorithm" do
|
7
|
+
experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
8
|
+
expect(Split::Algorithms::Whiplash.choose_alternative(experiment).class).to eq(Split::Alternative)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should return one of the results" do
|
12
|
+
experiment = Split::ExperimentCatalog.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
13
|
+
expect(['red', 'blue']).to include Split::Algorithms::Whiplash.choose_alternative(experiment).name
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should guess floats" do
|
17
|
+
expect(Split::Algorithms::Whiplash.send(:arm_guess, 0, 0).class).to eq(Float)
|
18
|
+
expect(Split::Algorithms::Whiplash.send(:arm_guess, 1, 0).class).to eq(Float)
|
19
|
+
expect(Split::Algorithms::Whiplash.send(:arm_guess, 2, 1).class).to eq(Float)
|
20
|
+
expect(Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class).to eq(Float)
|
21
|
+
expect(Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class).to eq(Float)
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'split/alternative'
|
4
|
+
|
5
|
+
describe Split::Alternative do
|
6
|
+
|
7
|
+
let(:alternative) {
|
8
|
+
Split::Alternative.new('Basket', 'basket_text')
|
9
|
+
}
|
10
|
+
|
11
|
+
let(:alternative2) {
|
12
|
+
Split::Alternative.new('Cart', 'basket_text')
|
13
|
+
}
|
14
|
+
|
15
|
+
let!(:experiment) {
|
16
|
+
Split::ExperimentCatalog.find_or_create({"basket_text" => ["purchase", "refund"]}, "Basket", "Cart")
|
17
|
+
}
|
18
|
+
|
19
|
+
let(:goal1) { "purchase" }
|
20
|
+
let(:goal2) { "refund" }
|
21
|
+
|
22
|
+
it "should have goals" do
|
23
|
+
expect(alternative.goals).to eq(["purchase", "refund"])
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should have and only return the name" do
|
27
|
+
expect(alternative.name).to eq('Basket')
|
28
|
+
end
|
29
|
+
|
30
|
+
describe 'weights' do
|
31
|
+
it "should set the weights" do
|
32
|
+
experiment = Split::Experiment.new('basket_text', :alternatives => [{'Basket' => 0.6}, {"Cart" => 0.4}])
|
33
|
+
first = experiment.alternatives[0]
|
34
|
+
expect(first.name).to eq('Basket')
|
35
|
+
expect(first.weight).to eq(0.6)
|
36
|
+
|
37
|
+
second = experiment.alternatives[1]
|
38
|
+
expect(second.name).to eq('Cart')
|
39
|
+
expect(second.weight).to eq(0.4)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "accepts probability on alternatives" do
|
43
|
+
Split.configuration.experiments = {
|
44
|
+
:my_experiment => {
|
45
|
+
:alternatives => [
|
46
|
+
{ :name => "control_opt", :percent => 67 },
|
47
|
+
{ :name => "second_opt", :percent => 10 },
|
48
|
+
{ :name => "third_opt", :percent => 23 },
|
49
|
+
]
|
50
|
+
}
|
51
|
+
}
|
52
|
+
experiment = Split::Experiment.new(:my_experiment)
|
53
|
+
first = experiment.alternatives[0]
|
54
|
+
expect(first.name).to eq('control_opt')
|
55
|
+
expect(first.weight).to eq(0.67)
|
56
|
+
|
57
|
+
second = experiment.alternatives[1]
|
58
|
+
expect(second.name).to eq('second_opt')
|
59
|
+
expect(second.weight).to eq(0.1)
|
60
|
+
end
|
61
|
+
|
62
|
+
it "accepts probability on some alternatives" do
|
63
|
+
Split.configuration.experiments = {
|
64
|
+
:my_experiment => {
|
65
|
+
:alternatives => [
|
66
|
+
{ :name => "control_opt", :percent => 34 },
|
67
|
+
"second_opt",
|
68
|
+
{ :name => "third_opt", :percent => 23 },
|
69
|
+
"fourth_opt",
|
70
|
+
],
|
71
|
+
}
|
72
|
+
}
|
73
|
+
experiment = Split::Experiment.new(:my_experiment)
|
74
|
+
alts = experiment.alternatives
|
75
|
+
[
|
76
|
+
["control_opt", 0.34],
|
77
|
+
["second_opt", 0.215],
|
78
|
+
["third_opt", 0.23],
|
79
|
+
["fourth_opt", 0.215]
|
80
|
+
].each do |h|
|
81
|
+
name, weight = h
|
82
|
+
alt = alts.shift
|
83
|
+
expect(alt.name).to eq(name)
|
84
|
+
expect(alt.weight).to eq(weight)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
#
|
88
|
+
it "allows name param without probability" do
|
89
|
+
Split.configuration.experiments = {
|
90
|
+
:my_experiment => {
|
91
|
+
:alternatives => [
|
92
|
+
{ :name => "control_opt" },
|
93
|
+
"second_opt",
|
94
|
+
{ :name => "third_opt", :percent => 64 },
|
95
|
+
],
|
96
|
+
}
|
97
|
+
}
|
98
|
+
experiment = Split::Experiment.new(:my_experiment)
|
99
|
+
alts = experiment.alternatives
|
100
|
+
[
|
101
|
+
["control_opt", 0.18],
|
102
|
+
["second_opt", 0.18],
|
103
|
+
["third_opt", 0.64],
|
104
|
+
].each do |h|
|
105
|
+
name, weight = h
|
106
|
+
alt = alts.shift
|
107
|
+
expect(alt.name).to eq(name)
|
108
|
+
expect(alt.weight).to eq(weight)
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should have a default participation count of 0" do
|
114
|
+
expect(alternative.participant_count).to eq(0)
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should have a default completed count of 0 for each goal" do
|
118
|
+
expect(alternative.completed_count).to eq(0)
|
119
|
+
expect(alternative.completed_count(goal1)).to eq(0)
|
120
|
+
expect(alternative.completed_count(goal2)).to eq(0)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should belong to an experiment" do
|
124
|
+
expect(alternative.experiment.name).to eq(experiment.name)
|
125
|
+
end
|
126
|
+
|
127
|
+
it "should save to redis" do
|
128
|
+
alternative.save
|
129
|
+
expect(Split.redis.exists('basket_text:Basket')).to be true
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should increment participation count" do
|
133
|
+
old_participant_count = alternative.participant_count
|
134
|
+
alternative.increment_participation
|
135
|
+
expect(alternative.participant_count).to eq(old_participant_count+1)
|
136
|
+
end
|
137
|
+
|
138
|
+
it "should increment completed count for each goal" do
|
139
|
+
old_default_completed_count = alternative.completed_count
|
140
|
+
old_completed_count_for_goal1 = alternative.completed_count(goal1)
|
141
|
+
old_completed_count_for_goal2 = alternative.completed_count(goal2)
|
142
|
+
|
143
|
+
alternative.increment_completion
|
144
|
+
alternative.increment_completion(goal1)
|
145
|
+
alternative.increment_completion(goal2)
|
146
|
+
|
147
|
+
expect(alternative.completed_count).to eq(old_default_completed_count+1)
|
148
|
+
expect(alternative.completed_count(goal1)).to eq(old_completed_count_for_goal1+1)
|
149
|
+
expect(alternative.completed_count(goal2)).to eq(old_completed_count_for_goal2+1)
|
150
|
+
end
|
151
|
+
|
152
|
+
it "can be reset" do
|
153
|
+
alternative.participant_count = 10
|
154
|
+
alternative.set_completed_count(4, goal1)
|
155
|
+
alternative.set_completed_count(5, goal2)
|
156
|
+
alternative.set_completed_count(6)
|
157
|
+
alternative.reset
|
158
|
+
expect(alternative.participant_count).to eq(0)
|
159
|
+
expect(alternative.completed_count(goal1)).to eq(0)
|
160
|
+
expect(alternative.completed_count(goal2)).to eq(0)
|
161
|
+
expect(alternative.completed_count).to eq(0)
|
162
|
+
end
|
163
|
+
|
164
|
+
it "should know if it is the control of an experiment" do
|
165
|
+
expect(alternative.control?).to be_truthy
|
166
|
+
expect(alternative2.control?).to be_falsey
|
167
|
+
end
|
168
|
+
|
169
|
+
describe 'unfinished_count' do
|
170
|
+
it "should be difference between participant and completed counts" do
|
171
|
+
alternative.increment_participation
|
172
|
+
expect(alternative.unfinished_count).to eq(alternative.participant_count)
|
173
|
+
end
|
174
|
+
|
175
|
+
it "should return the correct unfinished_count" do
|
176
|
+
alternative.participant_count = 10
|
177
|
+
alternative.set_completed_count(4, goal1)
|
178
|
+
alternative.set_completed_count(3, goal2)
|
179
|
+
alternative.set_completed_count(2)
|
180
|
+
|
181
|
+
expect(alternative.unfinished_count).to eq(1)
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe 'conversion rate' do
|
186
|
+
it "should be 0 if there are no conversions" do
|
187
|
+
expect(alternative.completed_count).to eq(0)
|
188
|
+
expect(alternative.conversion_rate).to eq(0)
|
189
|
+
end
|
190
|
+
|
191
|
+
it "calculate conversion rate" do
|
192
|
+
expect(alternative).to receive(:participant_count).exactly(6).times.and_return(10)
|
193
|
+
expect(alternative).to receive(:completed_count).and_return(4)
|
194
|
+
expect(alternative.conversion_rate).to eq(0.4)
|
195
|
+
|
196
|
+
expect(alternative).to receive(:completed_count).with(goal1).and_return(5)
|
197
|
+
expect(alternative.conversion_rate(goal1)).to eq(0.5)
|
198
|
+
|
199
|
+
expect(alternative).to receive(:completed_count).with(goal2).and_return(6)
|
200
|
+
expect(alternative.conversion_rate(goal2)).to eq(0.6)
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
describe "probability winner" do
|
205
|
+
before do
|
206
|
+
experiment.calc_winning_alternatives
|
207
|
+
end
|
208
|
+
|
209
|
+
it "should have a probability of being the winning alternative (p_winner)" do
|
210
|
+
expect(alternative.p_winner).not_to be_nil
|
211
|
+
end
|
212
|
+
|
213
|
+
it "should have a probability of being the winner for each goal" do
|
214
|
+
expect(alternative.p_winner(goal1)).not_to be_nil
|
215
|
+
end
|
216
|
+
|
217
|
+
it "should be possible to set the p_winner" do
|
218
|
+
alternative.set_p_winner(0.5)
|
219
|
+
expect(alternative.p_winner).to eq(0.5)
|
220
|
+
end
|
221
|
+
|
222
|
+
it "should be possible to set the p_winner for each goal" do
|
223
|
+
alternative.set_p_winner(0.5, goal1)
|
224
|
+
expect(alternative.p_winner(goal1)).to eq(0.5)
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
describe 'z score' do
|
229
|
+
|
230
|
+
it "should return an error string when the control has 0 people" do
|
231
|
+
expect(alternative2.z_score).to eq("Needs 30+ participants.")
|
232
|
+
expect(alternative2.z_score(goal1)).to eq("Needs 30+ participants.")
|
233
|
+
expect(alternative2.z_score(goal2)).to eq("Needs 30+ participants.")
|
234
|
+
end
|
235
|
+
|
236
|
+
it "should return an error string when the data is skewed or incomplete as per the np > 5 test" do
|
237
|
+
control = experiment.control
|
238
|
+
control.participant_count = 100
|
239
|
+
control.set_completed_count(50)
|
240
|
+
|
241
|
+
alternative2.participant_count = 50
|
242
|
+
alternative2.set_completed_count(1)
|
243
|
+
|
244
|
+
expect(alternative2.z_score).to eq("Needs 5+ conversions.")
|
245
|
+
end
|
246
|
+
|
247
|
+
it "should return a float for a z_score given proper data" do
|
248
|
+
control = experiment.control
|
249
|
+
control.participant_count = 120
|
250
|
+
control.set_completed_count(20)
|
251
|
+
|
252
|
+
alternative2.participant_count = 100
|
253
|
+
alternative2.set_completed_count(25)
|
254
|
+
|
255
|
+
expect(alternative2.z_score).to be_kind_of(Float)
|
256
|
+
expect(alternative2.z_score).to_not eq(0)
|
257
|
+
end
|
258
|
+
|
259
|
+
it "should correctly calculate a z_score given proper data" do
|
260
|
+
control = experiment.control
|
261
|
+
control.participant_count = 126
|
262
|
+
control.set_completed_count(89)
|
263
|
+
|
264
|
+
alternative2.participant_count = 142
|
265
|
+
alternative2.set_completed_count(119)
|
266
|
+
|
267
|
+
expect(alternative2.z_score.round(2)).to eq(2.58)
|
268
|
+
end
|
269
|
+
|
270
|
+
it "should be N/A for the control" do
|
271
|
+
control = experiment.control
|
272
|
+
expect(control.z_score).to eq('N/A')
|
273
|
+
expect(control.z_score(goal1)).to eq('N/A')
|
274
|
+
expect(control.z_score(goal2)).to eq('N/A')
|
275
|
+
end
|
276
|
+
|
277
|
+
it "should not blow up for Conversion Rates > 1" do
|
278
|
+
control = experiment.control
|
279
|
+
control.participant_count = 3474
|
280
|
+
control.set_completed_count(4244)
|
281
|
+
|
282
|
+
alternative2.participant_count = 3434
|
283
|
+
alternative2.set_completed_count(4358)
|
284
|
+
|
285
|
+
expect { control.z_score }.not_to raise_error
|
286
|
+
expect { alternative2.z_score }.not_to raise_error
|
287
|
+
end
|
288
|
+
end
|
289
|
+
|
290
|
+
describe "extra_info" do
|
291
|
+
it "reads saved value of recorded_info in redis" do
|
292
|
+
saved_recorded_info = {"key_1" => 1, "key_2" => "2"}
|
293
|
+
Split.redis.hset "#{alternative.experiment_name}:#{alternative.name}", 'recorded_info', saved_recorded_info.to_json
|
294
|
+
extra_info = alternative.extra_info
|
295
|
+
|
296
|
+
expect(extra_info).to eql(saved_recorded_info)
|
297
|
+
end
|
298
|
+
end
|
299
|
+
|
300
|
+
describe "record_extra_info" do
|
301
|
+
it "saves key" do
|
302
|
+
alternative.record_extra_info("signup", 1)
|
303
|
+
expect(alternative.extra_info["signup"]).to eql(1)
|
304
|
+
end
|
305
|
+
|
306
|
+
it "adds value to saved key's value second argument is number" do
|
307
|
+
alternative.record_extra_info("signup", 1)
|
308
|
+
alternative.record_extra_info("signup", 2)
|
309
|
+
expect(alternative.extra_info["signup"]).to eql(3)
|
310
|
+
end
|
311
|
+
|
312
|
+
it "sets saved's key value to the second argument if it's a string" do
|
313
|
+
alternative.record_extra_info("signup", "Value 1")
|
314
|
+
expect(alternative.extra_info["signup"]).to eql("Value 1")
|
315
|
+
|
316
|
+
alternative.record_extra_info("signup", "Value 2")
|
317
|
+
expect(alternative.extra_info["signup"]).to eql("Value 2")
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'split/combined_experiments_helper'
|
4
|
+
|
5
|
+
describe Split::CombinedExperimentsHelper do
|
6
|
+
include Split::CombinedExperimentsHelper
|
7
|
+
|
8
|
+
describe 'ab_combined_test' do
|
9
|
+
let!(:config_enabled) { true }
|
10
|
+
let!(:combined_experiments) { [:exp_1_click, :exp_1_scroll ]}
|
11
|
+
let!(:allow_multiple_experiments) { true }
|
12
|
+
|
13
|
+
before do
|
14
|
+
Split.configuration.experiments = {
|
15
|
+
:combined_exp_1 => {
|
16
|
+
:alternatives => [ {"control"=> 0.5}, {"test-alt"=> 0.5} ],
|
17
|
+
:metric => :my_metric,
|
18
|
+
:combined_experiments => combined_experiments
|
19
|
+
}
|
20
|
+
}
|
21
|
+
Split.configuration.enabled = config_enabled
|
22
|
+
Split.configuration.allow_multiple_experiments = allow_multiple_experiments
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'without config enabled' do
|
26
|
+
let!(:config_enabled) { false }
|
27
|
+
|
28
|
+
it "raises an error" do
|
29
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'multiple experiments disabled' do
|
34
|
+
let!(:allow_multiple_experiments) { false }
|
35
|
+
|
36
|
+
it "raises an error if multiple experiments is disabled" do
|
37
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'without combined experiments' do
|
42
|
+
let!(:combined_experiments) { nil }
|
43
|
+
|
44
|
+
it "raises an error" do
|
45
|
+
expect(lambda { ab_combined_test :combined_exp_1 }).to raise_error(Split::InvalidExperimentsFormatError )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "uses same alternative for all sub experiments and returns the alternative" do
|
50
|
+
allow(self).to receive(:get_alternative) { "test-alt" }
|
51
|
+
expect(self).to receive(:ab_test).with(:exp_1_click, {"control"=>0.5}, {"test-alt"=>0.5}) { "test-alt" }
|
52
|
+
expect(self).to receive(:ab_test).with(:exp_1_scroll, [{"control" => 0, "test-alt" => 1}])
|
53
|
+
|
54
|
+
expect(ab_combined_test('combined_exp_1')).to eq('test-alt')
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|