split 0.4.6 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. data/.travis.yml +11 -3
  2. data/CHANGELOG.mdown +22 -1
  3. data/CONTRIBUTING.md +10 -0
  4. data/LICENSE +1 -1
  5. data/README.mdown +235 -60
  6. data/lib/split.rb +8 -9
  7. data/lib/split/algorithms.rb +3 -0
  8. data/lib/split/algorithms/weighted_sample.rb +17 -0
  9. data/lib/split/algorithms/whiplash.rb +35 -0
  10. data/lib/split/alternative.rb +12 -4
  11. data/lib/split/configuration.rb +91 -1
  12. data/lib/split/dashboard/helpers.rb +3 -3
  13. data/lib/split/dashboard/views/_experiment.erb +1 -1
  14. data/lib/split/exceptions.rb +4 -0
  15. data/lib/split/experiment.rb +112 -24
  16. data/lib/split/extensions.rb +3 -0
  17. data/lib/split/extensions/array.rb +4 -0
  18. data/lib/split/extensions/string.rb +15 -0
  19. data/lib/split/helper.rb +87 -55
  20. data/lib/split/metric.rb +68 -0
  21. data/lib/split/persistence.rb +28 -0
  22. data/lib/split/persistence/cookie_adapter.rb +44 -0
  23. data/lib/split/persistence/session_adapter.rb +28 -0
  24. data/lib/split/trial.rb +43 -0
  25. data/lib/split/version.rb +3 -3
  26. data/spec/algorithms/weighted_sample_spec.rb +18 -0
  27. data/spec/algorithms/whiplash_spec.rb +23 -0
  28. data/spec/alternative_spec.rb +81 -9
  29. data/spec/configuration_spec.rb +61 -9
  30. data/spec/dashboard_helpers_spec.rb +2 -5
  31. data/spec/dashboard_spec.rb +0 -2
  32. data/spec/experiment_spec.rb +144 -74
  33. data/spec/helper_spec.rb +234 -29
  34. data/spec/metric_spec.rb +30 -0
  35. data/spec/persistence/cookie_adapter_spec.rb +31 -0
  36. data/spec/persistence/session_adapter_spec.rb +31 -0
  37. data/spec/persistence_spec.rb +33 -0
  38. data/spec/spec_helper.rb +12 -0
  39. data/spec/support/cookies_mock.rb +19 -0
  40. data/spec/trial_spec.rb +59 -0
  41. data/split.gemspec +7 -3
  42. metadata +58 -29
  43. data/Guardfile +0 -5
@@ -1,6 +1,6 @@
1
1
  module Split
2
2
  MAJOR = 0
3
- MINOR = 4
4
- TINY = 6
5
- VERSION = [MAJOR, MINOR, TINY].join('.')
3
+ MINOR = 5
4
+ PATCH = 0
5
+ VERSION = [MAJOR, MINOR, PATCH].join('.')
6
6
  end
@@ -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
@@ -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 "does something" do
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
@@ -1,14 +1,66 @@
1
1
  require 'spec_helper'
2
2
 
3
3
  describe Split::Configuration do
4
- it "should provide default values" do
5
- config = Split::Configuration.new
6
-
7
- config.ignore_ip_addresses.should eql([])
8
- config.robot_regex.should eql(/\b(Baidu|Gigabot|Googlebot|libwww-perl|lwp-trivial|msnbot|SiteUptime|Slurp|WordPress|ZIBB|ZyBorg)\b/i)
9
- config.db_failover.should be_false
10
- config.db_failover_on_db_error.should be_a Proc
11
- config.allow_multiple_experiments.should be_false
12
- config.enabled.should be_true
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.96 < z < 2.57 as 95% confident" do
13
- confidence_level(2.12).should eql('95% confidence')
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
@@ -9,8 +9,6 @@ describe Split::Dashboard do
9
9
  @app ||= Split::Dashboard
10
10
  end
11
11
 
12
- before(:each) { Split.redis.flushall }
13
-
14
12
  it "should respond to /" do
15
13
  get '/'
16
14
  last_response.should be_ok
@@ -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
- before(:each) { Split.redis.flushall }
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
- it "should have a name" do
8
- experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
9
- experiment.name.should eql('basket_text')
10
- end
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
- it "should have alternatives" do
13
- experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
14
- experiment.alternatives.length.should be 2
15
- end
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
- it "should save to redis" do
18
- experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
19
- experiment.save
20
- Split.redis.exists('basket_text').should be true
21
- end
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
- it "should save the start time to redis" do
24
- experiment_start_time = Time.parse("Sat Mar 03 14:01:03")
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
- Split::Experiment.find('basket_text').start_time.should == experiment_start_time
30
- end
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
- it "should handle not having a start time" do
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
- Split.redis.hdel(:experiment_start_times, experiment.name)
54
+ Split::Experiment.find('basket_text').start_time.should == nil
55
+ end
39
56
 
40
- Split::Experiment.find('basket_text').start_time.should == nil
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
- it "should not create duplicates when saving multiple times" do
44
- experiment = Split::Experiment.new('basket_text', 'Basket', "Cart")
45
- experiment.save
46
- experiment.save
47
- Split.redis.exists('basket_text').should be true
48
- Split.redis.lrange('basket_text', 0, -1).should eql(['Basket', "Cart"])
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