split 0.4.6 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.travis.yml +11 -3
- data/CHANGELOG.mdown +22 -1
- data/CONTRIBUTING.md +10 -0
- data/LICENSE +1 -1
- data/README.mdown +235 -60
- data/lib/split.rb +8 -9
- data/lib/split/algorithms.rb +3 -0
- data/lib/split/algorithms/weighted_sample.rb +17 -0
- data/lib/split/algorithms/whiplash.rb +35 -0
- data/lib/split/alternative.rb +12 -4
- data/lib/split/configuration.rb +91 -1
- data/lib/split/dashboard/helpers.rb +3 -3
- data/lib/split/dashboard/views/_experiment.erb +1 -1
- data/lib/split/exceptions.rb +4 -0
- data/lib/split/experiment.rb +112 -24
- data/lib/split/extensions.rb +3 -0
- data/lib/split/extensions/array.rb +4 -0
- data/lib/split/extensions/string.rb +15 -0
- data/lib/split/helper.rb +87 -55
- data/lib/split/metric.rb +68 -0
- data/lib/split/persistence.rb +28 -0
- data/lib/split/persistence/cookie_adapter.rb +44 -0
- data/lib/split/persistence/session_adapter.rb +28 -0
- data/lib/split/trial.rb +43 -0
- data/lib/split/version.rb +3 -3
- data/spec/algorithms/weighted_sample_spec.rb +18 -0
- data/spec/algorithms/whiplash_spec.rb +23 -0
- data/spec/alternative_spec.rb +81 -9
- data/spec/configuration_spec.rb +61 -9
- data/spec/dashboard_helpers_spec.rb +2 -5
- data/spec/dashboard_spec.rb +0 -2
- data/spec/experiment_spec.rb +144 -74
- data/spec/helper_spec.rb +234 -29
- data/spec/metric_spec.rb +30 -0
- data/spec/persistence/cookie_adapter_spec.rb +31 -0
- data/spec/persistence/session_adapter_spec.rb +31 -0
- data/spec/persistence_spec.rb +33 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/cookies_mock.rb +19 -0
- data/spec/trial_spec.rb +59 -0
- data/split.gemspec +7 -3
- metadata +58 -29
- data/Guardfile +0 -5
data/lib/split/version.rb
CHANGED
@@ -0,0 +1,18 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Split::Algorithms::WeightedSample do
|
4
|
+
it "should return an alternative" do
|
5
|
+
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
|
6
|
+
Split::Algorithms::WeightedSample.choose_alternative(experiment).class.should == Split::Alternative
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should always return a heavily weighted option" do
|
10
|
+
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 100}, {'red' => 0 })
|
11
|
+
Split::Algorithms::WeightedSample.choose_alternative(experiment).name.should == 'blue'
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should return one of the results" do
|
15
|
+
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
16
|
+
['red', 'blue'].should include Split::Algorithms::WeightedSample.choose_alternative(experiment).name
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Split::Algorithms::Whiplash do
|
4
|
+
|
5
|
+
it "should return an algorithm" do
|
6
|
+
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
7
|
+
Split::Algorithms::Whiplash.choose_alternative(experiment).class.should == Split::Alternative
|
8
|
+
end
|
9
|
+
|
10
|
+
it "should return one of the results" do
|
11
|
+
experiment = Split::Experiment.find_or_create('link_color', {'blue' => 1}, {'red' => 1 })
|
12
|
+
['red', 'blue'].should include Split::Algorithms::Whiplash.choose_alternative(experiment).name
|
13
|
+
end
|
14
|
+
|
15
|
+
it "should guess floats" do
|
16
|
+
Split::Algorithms::Whiplash.send(:arm_guess, 0, 0).class.should == Float
|
17
|
+
Split::Algorithms::Whiplash.send(:arm_guess, 1, 0).class.should == Float
|
18
|
+
Split::Algorithms::Whiplash.send(:arm_guess, 2, 1).class.should == Float
|
19
|
+
Split::Algorithms::Whiplash.send(:arm_guess, 1000, 5).class.should == Float
|
20
|
+
Split::Algorithms::Whiplash.send(:arm_guess, 10, -2).class.should == Float
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
data/spec/alternative_spec.rb
CHANGED
@@ -2,19 +2,81 @@ require 'spec_helper'
|
|
2
2
|
require 'split/alternative'
|
3
3
|
|
4
4
|
describe Split::Alternative do
|
5
|
-
before(:each) { Split.redis.flushall }
|
6
5
|
|
7
6
|
it "should have a name" do
|
8
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
7
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
9
8
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
10
9
|
alternative.name.should eql('Basket')
|
11
10
|
end
|
12
11
|
|
13
12
|
it "return only the name" do
|
14
|
-
experiment = Split::Experiment.new('basket_text', {'Basket' => 0.6}, {"Cart" => 0.4})
|
13
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => [{'Basket' => 0.6}, {"Cart" => 0.4}])
|
15
14
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
16
15
|
alternative.name.should eql('Basket')
|
17
16
|
end
|
17
|
+
|
18
|
+
describe 'weights' do
|
19
|
+
|
20
|
+
it "should set the weights" do
|
21
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => [{'Basket' => 0.6}, {"Cart" => 0.4}])
|
22
|
+
first = experiment.alternatives[0]
|
23
|
+
first.name.should == 'Basket'
|
24
|
+
first.weight.should == 0.6
|
25
|
+
|
26
|
+
second = experiment.alternatives[1]
|
27
|
+
second.name.should == 'Cart'
|
28
|
+
second.weight.should == 0.4
|
29
|
+
end
|
30
|
+
|
31
|
+
it "accepts probability on alternatives" do
|
32
|
+
Split.configuration.experiments = {
|
33
|
+
:my_experiment => {
|
34
|
+
:alternatives => [
|
35
|
+
{ :name => "control_opt", :percent => 67 },
|
36
|
+
{ :name => "second_opt", :percent => 10 },
|
37
|
+
{ :name => "third_opt", :percent => 23 },
|
38
|
+
],
|
39
|
+
}
|
40
|
+
}
|
41
|
+
experiment = Split::Experiment.find(:my_experiment)
|
42
|
+
first = experiment.alternatives[0]
|
43
|
+
first.name.should == 'control_opt'
|
44
|
+
first.weight.should == 0.67
|
45
|
+
|
46
|
+
second = experiment.alternatives[1]
|
47
|
+
second.name.should == 'second_opt'
|
48
|
+
second.weight.should == 0.1
|
49
|
+
end
|
50
|
+
|
51
|
+
# it "accepts probability on some alternatives" do
|
52
|
+
# Split.configuration.experiments[:my_experiment] = {
|
53
|
+
# :alternatives => [
|
54
|
+
# { :name => "control_opt", :percent => 34 },
|
55
|
+
# "second_opt",
|
56
|
+
# { :name => "third_opt", :percent => 23 },
|
57
|
+
# "fourth_opt",
|
58
|
+
# ],
|
59
|
+
# }
|
60
|
+
# should start_experiment(:my_experiment).with({"control_opt" => 0.34}, {"second_opt" => 0.215}, {"third_opt" => 0.23}, {"fourth_opt" => 0.215})
|
61
|
+
# ab_test :my_experiment
|
62
|
+
# end
|
63
|
+
#
|
64
|
+
# it "allows name param without probability" do
|
65
|
+
# Split.configuration.experiments[:my_experiment] = {
|
66
|
+
# :alternatives => [
|
67
|
+
# { :name => "control_opt" },
|
68
|
+
# "second_opt",
|
69
|
+
# { :name => "third_opt", :percent => 64 },
|
70
|
+
# ],
|
71
|
+
# }
|
72
|
+
# should start_experiment(:my_experiment).with({"control_opt" => 0.18}, {"second_opt" => 0.18}, {"third_opt" => 0.64})
|
73
|
+
# ab_test :my_experiment
|
74
|
+
# end
|
75
|
+
|
76
|
+
it "should set the weights from a configuration file" do
|
77
|
+
|
78
|
+
end
|
79
|
+
end
|
18
80
|
|
19
81
|
it "should have a default participation count of 0" do
|
20
82
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
@@ -27,7 +89,7 @@ describe Split::Alternative do
|
|
27
89
|
end
|
28
90
|
|
29
91
|
it "should belong to an experiment" do
|
30
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
92
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
31
93
|
experiment.save
|
32
94
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
33
95
|
alternative.experiment.name.should eql(experiment.name)
|
@@ -40,7 +102,7 @@ describe Split::Alternative do
|
|
40
102
|
end
|
41
103
|
|
42
104
|
it "should increment participation count" do
|
43
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
105
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
44
106
|
experiment.save
|
45
107
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
46
108
|
old_participant_count = alternative.participant_count
|
@@ -51,7 +113,7 @@ describe Split::Alternative do
|
|
51
113
|
end
|
52
114
|
|
53
115
|
it "should increment completed count" do
|
54
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
116
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
55
117
|
experiment.save
|
56
118
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
57
119
|
old_completed_count = alternative.participant_count
|
@@ -71,7 +133,7 @@ describe Split::Alternative do
|
|
71
133
|
end
|
72
134
|
|
73
135
|
it "should know if it is the control of an experiment" do
|
74
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
136
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
75
137
|
experiment.save
|
76
138
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
77
139
|
alternative.control?.should be_true
|
@@ -79,6 +141,16 @@ describe Split::Alternative do
|
|
79
141
|
alternative.control?.should be_false
|
80
142
|
end
|
81
143
|
|
144
|
+
describe 'unfinished_count' do
|
145
|
+
it "should be difference between participant and completed counts" do
|
146
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"])
|
147
|
+
experiment.save
|
148
|
+
alternative = Split::Alternative.new('Basket', 'basket_text')
|
149
|
+
alternative.increment_participation
|
150
|
+
alternative.unfinished_count.should eql(alternative.participant_count)
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
82
154
|
describe 'conversion rate' do
|
83
155
|
it "should be 0 if there are no conversions" do
|
84
156
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
@@ -86,7 +158,7 @@ describe Split::Alternative do
|
|
86
158
|
alternative.conversion_rate.should eql(0)
|
87
159
|
end
|
88
160
|
|
89
|
-
it "
|
161
|
+
it "calculate conversion rate" do
|
90
162
|
alternative = Split::Alternative.new('Basket', 'basket_text')
|
91
163
|
alternative.stub(:participant_count).and_return(10)
|
92
164
|
alternative.stub(:completed_count).and_return(4)
|
@@ -109,4 +181,4 @@ describe Split::Alternative do
|
|
109
181
|
control.z_score.should eql('N/A')
|
110
182
|
end
|
111
183
|
end
|
112
|
-
end
|
184
|
+
end
|
data/spec/configuration_spec.rb
CHANGED
@@ -1,14 +1,66 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe Split::Configuration do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
config.
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
config.
|
4
|
+
|
5
|
+
before(:each) { @config = Split::Configuration.new }
|
6
|
+
|
7
|
+
it "should provide a default value for ignore_ip_addresses" do
|
8
|
+
@config.ignore_ip_addresses.should eql([])
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should provide default values for db failover" do
|
12
|
+
@config.db_failover.should be_false
|
13
|
+
@config.db_failover_on_db_error.should be_a Proc
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should not allow multiple experiments by default" do
|
17
|
+
@config.allow_multiple_experiments.should be_false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should be enabled by default" do
|
21
|
+
@config.enabled.should be_true
|
22
|
+
end
|
23
|
+
|
24
|
+
it "disabled is the opposite of enabled" do
|
25
|
+
@config.enabled = false
|
26
|
+
@config.disabled?.should be_true
|
27
|
+
end
|
28
|
+
|
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|
|
31
|
+
@config.robot_regex.should =~ robot
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should use the session adapter for persistence by default" do
|
36
|
+
@config.persistence.should eq(Split::Persistence::SessionAdapter)
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should load a metric" do
|
40
|
+
@config.experiments = {:my_experiment=>
|
41
|
+
{:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}}
|
42
|
+
|
43
|
+
@config.metrics.should_not be_nil
|
44
|
+
@config.metrics.keys.should == [:my_metric]
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should allow loading of experiment using experment_for" do
|
48
|
+
@config.experiments = {:my_experiment=>
|
49
|
+
{:alternatives=>["control_opt", "other_opt"], :metric=>:my_metric}}
|
50
|
+
@config.experiment_for(:my_experiment).should == {:alternatives=>["control_opt", ["other_opt"]]}
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should normalize experiments" do
|
54
|
+
@config.experiments = {
|
55
|
+
:my_experiment => {
|
56
|
+
:alternatives => [
|
57
|
+
{ :name => "control_opt", :percent => 67 },
|
58
|
+
{ :name => "second_opt", :percent => 10 },
|
59
|
+
{ :name => "third_opt", :percent => 23 },
|
60
|
+
],
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
@config.normalized_experiments.should == {:my_experiment=>{:alternatives=>[{"control_opt"=>0.67}, [{"second_opt"=>0.1}, {"third_opt"=>0.23}]]}}
|
13
65
|
end
|
14
66
|
end
|
@@ -9,12 +9,9 @@ describe Split::DashboardHelpers do
|
|
9
9
|
confidence_level(Complex(2e-18, -0.03)).should eql('No Change')
|
10
10
|
end
|
11
11
|
|
12
|
-
it "should consider a z-score of 1.
|
13
|
-
confidence_level(
|
12
|
+
it "should consider a z-score of 1.645 < z < 1.96 as 95% confident" do
|
13
|
+
confidence_level(1.80).should eql('95% confidence')
|
14
14
|
end
|
15
15
|
|
16
|
-
it "should consider a z-score of -1.96 > z > -2.57 as 95% confident" do
|
17
|
-
confidence_level(-2.12).should eql('95% confidence')
|
18
|
-
end
|
19
16
|
end
|
20
17
|
end
|
data/spec/dashboard_spec.rb
CHANGED
data/spec/experiment_spec.rb
CHANGED
@@ -1,56 +1,135 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
require 'split/experiment'
|
3
|
+
require 'split/algorithms'
|
4
|
+
require 'time'
|
3
5
|
|
4
6
|
describe Split::Experiment do
|
5
|
-
|
7
|
+
context "with an experiment" do
|
8
|
+
let(:experiment) { Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"]) }
|
9
|
+
|
10
|
+
it "should have a name" do
|
11
|
+
experiment.name.should eql('basket_text')
|
12
|
+
end
|
6
13
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
14
|
+
it "should have alternatives" do
|
15
|
+
experiment.alternatives.length.should be 2
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should have alternatives with correct names" do
|
19
|
+
experiment.alternatives.collect{|a| a.name}.should == ['Basket', 'Cart']
|
20
|
+
end
|
21
|
+
|
22
|
+
it "should be resettable by default" do
|
23
|
+
experiment.resettable.should be_true
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should save to redis" do
|
27
|
+
experiment.save
|
28
|
+
Split.redis.exists('basket_text').should be true
|
29
|
+
end
|
11
30
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
31
|
+
it "should save the start time to redis" do
|
32
|
+
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
33
|
+
Time.stub(:now => experiment_start_time)
|
34
|
+
experiment.save
|
16
35
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
36
|
+
Split::Experiment.find('basket_text').start_time.should == experiment_start_time
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should save the selected algorithm to redis" do
|
40
|
+
experiment_algorithm = Split::Algorithms::Whiplash
|
41
|
+
experiment.algorithm = experiment_algorithm
|
42
|
+
experiment.save
|
22
43
|
|
23
|
-
|
24
|
-
|
25
|
-
Time.stub(:now => experiment_start_time)
|
26
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
27
|
-
experiment.save
|
44
|
+
Split::Experiment.find('basket_text').algorithm.should == experiment_algorithm
|
45
|
+
end
|
28
46
|
|
29
|
-
|
30
|
-
|
47
|
+
it "should handle not having a start time" do
|
48
|
+
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
49
|
+
Time.stub(:now => experiment_start_time)
|
50
|
+
experiment.save
|
31
51
|
|
32
|
-
|
33
|
-
experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
|
34
|
-
Time.stub(:now => experiment_start_time)
|
35
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
36
|
-
experiment.save
|
52
|
+
Split.redis.hdel(:experiment_start_times, experiment.name)
|
37
53
|
|
38
|
-
|
54
|
+
Split::Experiment.find('basket_text').start_time.should == nil
|
55
|
+
end
|
39
56
|
|
40
|
-
|
57
|
+
it "should not create duplicates when saving multiple times" do
|
58
|
+
experiment.save
|
59
|
+
experiment.save
|
60
|
+
Split.redis.exists('basket_text').should be true
|
61
|
+
Split.redis.lrange('basket_text', 0, -1).should eql(['Basket', "Cart"])
|
62
|
+
end
|
63
|
+
|
64
|
+
describe 'new record?' do
|
65
|
+
it "should know if it hasn't been saved yet" do
|
66
|
+
experiment.new_record?.should be_true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should know if it has been saved yet" do
|
70
|
+
experiment.save
|
71
|
+
experiment.new_record?.should be_false
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe 'find' do
|
76
|
+
it "should return an existing experiment" do
|
77
|
+
experiment.save
|
78
|
+
Split::Experiment.find('basket_text').name.should eql('basket_text')
|
79
|
+
end
|
80
|
+
|
81
|
+
it "should return an existing experiment" do
|
82
|
+
Split::Experiment.find('non_existent_experiment').should be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
describe 'control' do
|
87
|
+
it 'should be the first alternative' do
|
88
|
+
experiment.save
|
89
|
+
experiment.control.name.should eql('Basket')
|
90
|
+
end
|
91
|
+
end
|
41
92
|
end
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
93
|
+
describe 'initialization' do
|
94
|
+
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)
|
96
|
+
experiment.algorithm.should == Split::Algorithms::Whiplash
|
97
|
+
end
|
98
|
+
|
99
|
+
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
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
describe 'persistent configuration' do
|
106
|
+
|
107
|
+
it "should persist resettable in redis" do
|
108
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :resettable => false)
|
109
|
+
experiment.save
|
110
|
+
|
111
|
+
e = Split::Experiment.find('basket_text')
|
112
|
+
e.should == experiment
|
113
|
+
e.resettable.should be_false
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
it "should persist algorithm in redis" do
|
118
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => ['Basket', "Cart"], :algorithm => Split::Algorithms::Whiplash)
|
119
|
+
experiment.save
|
120
|
+
|
121
|
+
e = Split::Experiment.find('basket_text')
|
122
|
+
e.should == experiment
|
123
|
+
e.algorithm.should == Split::Algorithms::Whiplash
|
124
|
+
end
|
49
125
|
end
|
50
126
|
|
127
|
+
|
128
|
+
|
129
|
+
|
51
130
|
describe 'deleting' do
|
52
131
|
it 'should delete itself' do
|
53
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
132
|
+
experiment = Split::Experiment.new('basket_text', :alternative_names => [ 'Basket', "Cart"])
|
54
133
|
experiment.save
|
55
134
|
|
56
135
|
experiment.delete
|
@@ -66,39 +145,6 @@ describe Split::Experiment do
|
|
66
145
|
end
|
67
146
|
end
|
68
147
|
|
69
|
-
describe 'new record?' do
|
70
|
-
it "should know if it hasn't been saved yet" do
|
71
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
72
|
-
experiment.new_record?.should be_true
|
73
|
-
end
|
74
|
-
|
75
|
-
it "should know if it has been saved yet" do
|
76
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
77
|
-
experiment.save
|
78
|
-
experiment.new_record?.should be_false
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
describe 'find' do
|
83
|
-
it "should return an existing experiment" do
|
84
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
85
|
-
experiment.save
|
86
|
-
Split::Experiment.find('basket_text').name.should eql('basket_text')
|
87
|
-
end
|
88
|
-
|
89
|
-
it "should return an existing experiment" do
|
90
|
-
Split::Experiment.find('non_existent_experiment').should be_nil
|
91
|
-
end
|
92
|
-
end
|
93
|
-
|
94
|
-
describe 'control' do
|
95
|
-
it 'should be the first alternative' do
|
96
|
-
experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
|
97
|
-
experiment.save
|
98
|
-
experiment.control.name.should eql('Basket')
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
148
|
describe 'winner' do
|
103
149
|
it "should have no winner initially" do
|
104
150
|
experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
|
@@ -150,10 +196,24 @@ describe Split::Experiment do
|
|
150
196
|
experiment.version.should eql(1)
|
151
197
|
end
|
152
198
|
end
|
153
|
-
|
199
|
+
|
200
|
+
describe 'algorithm' do
|
201
|
+
let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green') }
|
202
|
+
|
203
|
+
it 'should use the default algorithm if none is specified' do
|
204
|
+
experiment.algorithm.should == Split.configuration.algorithm
|
205
|
+
end
|
206
|
+
|
207
|
+
it 'should use the user specified algorithm for this experiment if specified' do
|
208
|
+
experiment.algorithm = Split::Algorithms::Whiplash
|
209
|
+
experiment.algorithm.should == Split::Algorithms::Whiplash
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
154
213
|
describe 'next_alternative' do
|
214
|
+
let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green') }
|
215
|
+
|
155
216
|
it "should always return the winner if one exists" do
|
156
|
-
experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
|
157
217
|
green = Split::Alternative.new('green', 'link_color')
|
158
218
|
experiment.winner = 'green'
|
159
219
|
|
@@ -163,6 +223,19 @@ describe Split::Experiment do
|
|
163
223
|
experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red', 'green')
|
164
224
|
experiment.next_alternative.name.should eql('green')
|
165
225
|
end
|
226
|
+
|
227
|
+
it "should use the specified algorithm if a winner does not exist" do
|
228
|
+
Split.configuration.algorithm.should_receive(:choose_alternative).and_return(Split::Alternative.new('green', 'link_color'))
|
229
|
+
experiment.next_alternative.name.should eql('green')
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
describe 'single alternative' do
|
234
|
+
let(:experiment) { Split::Experiment.find_or_create('link_color', 'blue') }
|
235
|
+
|
236
|
+
it "should always return the color blue" do
|
237
|
+
experiment.next_alternative.name.should eql('blue')
|
238
|
+
end
|
166
239
|
end
|
167
240
|
|
168
241
|
describe 'changing an existing experiment' do
|
@@ -209,7 +282,4 @@ describe Split::Experiment do
|
|
209
282
|
same_experiment.alternatives.map(&:weight).should == [1, 2]
|
210
283
|
end
|
211
284
|
end
|
212
|
-
|
213
|
-
|
214
|
-
|
215
285
|
end
|