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.
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