split 0.4.6 → 0.5.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.
- 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
|