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/spec/helper_spec.rb
    CHANGED
    
    | @@ -5,13 +5,20 @@ require 'spec_helper' | |
| 5 5 | 
             
            describe Split::Helper do
         | 
| 6 6 | 
             
              include Split::Helper
         | 
| 7 7 |  | 
| 8 | 
            -
              before(:each) do
         | 
| 9 | 
            -
                Split.redis.flushall
         | 
| 10 | 
            -
                @session = {}
         | 
| 11 | 
            -
                params = nil
         | 
| 12 | 
            -
              end
         | 
| 13 | 
            -
             | 
| 14 8 | 
             
              describe "ab_test" do
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                it "should not raise an error when passed strings for alternatives" do
         | 
| 11 | 
            +
                  lambda { ab_test('xyz', '1', '2', '3') }.should_not raise_error
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
             | 
| 14 | 
            +
                it "should raise the appropriate error when passed integers for alternatives" do
         | 
| 15 | 
            +
                  lambda { ab_test('xyz', 1, 2, 3) }.should raise_error(ArgumentError)
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                it "should raise the appropriate error when passed symbols for alternatives" do
         | 
| 19 | 
            +
                  lambda { ab_test('xyz', :a, :b, :c) }.should raise_error(ArgumentError)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 15 22 | 
             
                it "should assign a random alternative to a new user when there are an equal number of alternatives assigned" do
         | 
| 16 23 | 
             
                  ab_test('link_color', 'blue', 'red')
         | 
| 17 24 | 
             
                  ['red', 'blue'].should include(ab_user['link_color'])
         | 
| @@ -74,12 +81,14 @@ describe Split::Helper do | |
| 74 81 | 
             
                  ab_test('link_color', {'blue' => 0.01}, 'red' => 0.2)
         | 
| 75 82 | 
             
                  experiment = Split::Experiment.find('link_color')
         | 
| 76 83 | 
             
                  experiment.alternative_names.should eql(['blue', 'red'])
         | 
| 84 | 
            +
                  # TODO: persist alternative weights
         | 
| 85 | 
            +
                  # experiment.alternatives.collect{|a| a.weight}.should == [0.01, 0.2]
         | 
| 77 86 | 
             
                end
         | 
| 78 87 |  | 
| 79 88 | 
             
                it "should only let a user participate in one experiment at a time" do
         | 
| 80 | 
            -
                  ab_test('link_color', 'blue', 'red')
         | 
| 89 | 
            +
                  link_color = ab_test('link_color', 'blue', 'red')
         | 
| 81 90 | 
             
                  ab_test('button_size', 'small', 'big')
         | 
| 82 | 
            -
                  ab_user | 
| 91 | 
            +
                  ab_user.should eql({'link_color' => link_color})
         | 
| 83 92 | 
             
                  big = Split::Alternative.new('big', 'button_size')
         | 
| 84 93 | 
             
                  big.participant_count.should eql(0)
         | 
| 85 94 | 
             
                  small = Split::Alternative.new('small', 'button_size')
         | 
| @@ -92,10 +101,19 @@ describe Split::Helper do | |
| 92 101 | 
             
                  end
         | 
| 93 102 | 
             
                  link_color = ab_test('link_color', 'blue', 'red')
         | 
| 94 103 | 
             
                  button_size = ab_test('button_size', 'small', 'big')
         | 
| 95 | 
            -
                  ab_user | 
| 104 | 
            +
                  ab_user.should eql({'link_color' => link_color, 'button_size' => button_size})
         | 
| 96 105 | 
             
                  button_size_alt = Split::Alternative.new(button_size, 'button_size')
         | 
| 97 106 | 
             
                  button_size_alt.participant_count.should eql(1)
         | 
| 98 107 | 
             
                end
         | 
| 108 | 
            +
             | 
| 109 | 
            +
                it "should not over-write a finished key when an experiment is on a later version" do
         | 
| 110 | 
            +
                  experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 111 | 
            +
                  experiment.increment_version
         | 
| 112 | 
            +
                  ab_user = { experiment.key => 'blue', experiment.finished_key => true }
         | 
| 113 | 
            +
                  finshed_session = ab_user.dup
         | 
| 114 | 
            +
                  ab_test('link_color', 'blue', 'red')
         | 
| 115 | 
            +
                  ab_user.should eql(finshed_session)
         | 
| 116 | 
            +
                end
         | 
| 99 117 | 
             
              end
         | 
| 100 118 |  | 
| 101 119 | 
             
              describe 'finished' do
         | 
| @@ -115,7 +133,7 @@ describe Split::Helper do | |
| 115 133 |  | 
| 116 134 | 
             
                it "should set experiment's finished key if reset is false" do
         | 
| 117 135 | 
             
                  finished(@experiment_name, :reset => false)
         | 
| 118 | 
            -
                   | 
| 136 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name, @experiment.finished_key => true)
         | 
| 119 137 | 
             
                end
         | 
| 120 138 |  | 
| 121 139 | 
             
                it 'should not increment the counter if reset is false and the experiment has been already finished' do
         | 
| @@ -125,36 +143,121 @@ describe Split::Helper do | |
| 125 143 | 
             
                end
         | 
| 126 144 |  | 
| 127 145 | 
             
                it "should clear out the user's participation from their session" do
         | 
| 128 | 
            -
                   | 
| 146 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name)
         | 
| 129 147 | 
             
                  finished(@experiment_name)
         | 
| 130 | 
            -
                   | 
| 148 | 
            +
                  ab_user.should == {}
         | 
| 131 149 | 
             
                end
         | 
| 132 150 |  | 
| 133 151 | 
             
                it "should not clear out the users session if reset is false" do
         | 
| 134 | 
            -
                   | 
| 152 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name)
         | 
| 135 153 | 
             
                  finished(@experiment_name, :reset => false)
         | 
| 136 | 
            -
                   | 
| 154 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name, @experiment.finished_key => true)
         | 
| 137 155 | 
             
                end
         | 
| 138 156 |  | 
| 139 157 | 
             
                it "should reset the users session when experiment is not versioned" do
         | 
| 140 | 
            -
                   | 
| 158 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name)
         | 
| 141 159 | 
             
                  finished(@experiment_name)
         | 
| 142 | 
            -
                   | 
| 160 | 
            +
                  ab_user.should eql({})
         | 
| 143 161 | 
             
                end
         | 
| 144 162 |  | 
| 145 163 | 
             
                it "should reset the users session when experiment is versioned" do
         | 
| 146 164 | 
             
                  @experiment.increment_version
         | 
| 147 165 | 
             
                  @alternative_name = ab_test(@experiment_name, *@alternatives)
         | 
| 148 166 |  | 
| 149 | 
            -
                   | 
| 167 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name)
         | 
| 150 168 | 
             
                  finished(@experiment_name)
         | 
| 151 | 
            -
                   | 
| 169 | 
            +
                  ab_user.should eql({})
         | 
| 152 170 | 
             
                end
         | 
| 153 171 |  | 
| 154 172 | 
             
                it "should do nothing where the experiment was not started by this user" do
         | 
| 155 | 
            -
                   | 
| 173 | 
            +
                  ab_user = nil
         | 
| 156 174 | 
             
                  lambda { finished('some_experiment_not_started_by_the_user') }.should_not raise_exception
         | 
| 157 175 | 
             
                end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                it 'should not be doing other tests when it has completed one that has :reset => false' do
         | 
| 178 | 
            +
                  ab_user[@experiment.key] = @alternative_name
         | 
| 179 | 
            +
                  ab_user[@experiment.finished_key] = true
         | 
| 180 | 
            +
                  doing_other_tests?(@experiment.key).should be false
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                it "passes reset option from config" do
         | 
| 184 | 
            +
                  Split.configuration.experiments = {
         | 
| 185 | 
            +
                    @experiment_name => {
         | 
| 186 | 
            +
                      :alternatives => @alternatives,
         | 
| 187 | 
            +
                      :resettable => false,
         | 
| 188 | 
            +
                    }
         | 
| 189 | 
            +
                  }
         | 
| 190 | 
            +
                  finished @experiment_name
         | 
| 191 | 
            +
                  ab_user.should eql(@experiment.key => @alternative_name, @experiment.finished_key => true)
         | 
| 192 | 
            +
                end
         | 
| 193 | 
            +
             | 
| 194 | 
            +
                context "with metric name" do
         | 
| 195 | 
            +
                  before { Split.configuration.experiments = {} }
         | 
| 196 | 
            +
                  before { Split::Alternative.stub(:new).and_call_original }
         | 
| 197 | 
            +
             | 
| 198 | 
            +
                  def should_finish_experiment(experiment_name, should_finish=true)
         | 
| 199 | 
            +
                    alts = Split.configuration.experiments[experiment_name][:alternatives]
         | 
| 200 | 
            +
                    experiment = Split::Experiment.find_or_create(experiment_name, *alts)
         | 
| 201 | 
            +
                    alt_name = ab_user[experiment.key] = alts.first
         | 
| 202 | 
            +
                    alt = mock('alternative')
         | 
| 203 | 
            +
                    alt.stub(:name).and_return(alt_name)
         | 
| 204 | 
            +
                    Split::Alternative.stub(:new).with(alt_name, experiment_name).and_return(alt)
         | 
| 205 | 
            +
                    if should_finish
         | 
| 206 | 
            +
                      alt.should_receive(:increment_completion)
         | 
| 207 | 
            +
                    else
         | 
| 208 | 
            +
                      alt.should_not_receive(:increment_completion)
         | 
| 209 | 
            +
                    end
         | 
| 210 | 
            +
                  end
         | 
| 211 | 
            +
             | 
| 212 | 
            +
                  it "completes the test" do
         | 
| 213 | 
            +
                    Split.configuration.experiments[:my_experiment] = {
         | 
| 214 | 
            +
                      :alternatives => [ "control_opt", "other_opt" ],
         | 
| 215 | 
            +
                      :metric => :my_metric
         | 
| 216 | 
            +
                    }
         | 
| 217 | 
            +
                    should_finish_experiment :my_experiment
         | 
| 218 | 
            +
                    finished :my_metric
         | 
| 219 | 
            +
                  end
         | 
| 220 | 
            +
             | 
| 221 | 
            +
                  it "completes all relevant tests" do
         | 
| 222 | 
            +
                    Split.configuration.experiments = {
         | 
| 223 | 
            +
                      :exp_1 => {
         | 
| 224 | 
            +
                        :alternatives => [ "1-1", "1-2" ],
         | 
| 225 | 
            +
                        :metric => :my_metric
         | 
| 226 | 
            +
                      },
         | 
| 227 | 
            +
                      :exp_2 => {
         | 
| 228 | 
            +
                        :alternatives => [ "2-1", "2-2" ],
         | 
| 229 | 
            +
                        :metric => :another_metric
         | 
| 230 | 
            +
                      },
         | 
| 231 | 
            +
                      :exp_3 => {
         | 
| 232 | 
            +
                        :alternatives => [ "3-1", "3-2" ],
         | 
| 233 | 
            +
                        :metric => :my_metric
         | 
| 234 | 
            +
                      },
         | 
| 235 | 
            +
                    }
         | 
| 236 | 
            +
                    should_finish_experiment :exp_1
         | 
| 237 | 
            +
                    should_finish_experiment :exp_2, false
         | 
| 238 | 
            +
                    should_finish_experiment :exp_3
         | 
| 239 | 
            +
                    finished :my_metric
         | 
| 240 | 
            +
                  end
         | 
| 241 | 
            +
             | 
| 242 | 
            +
                  it "passes reset option" do
         | 
| 243 | 
            +
                    Split.configuration.experiments[@experiment_name] = {
         | 
| 244 | 
            +
                      :alternatives => @alternatives,
         | 
| 245 | 
            +
                      :metric => :my_metric,
         | 
| 246 | 
            +
                      :resettable => false,
         | 
| 247 | 
            +
                    }
         | 
| 248 | 
            +
                    finished :my_metric
         | 
| 249 | 
            +
                    ab_user.should eql(@experiment.key => @alternative_name, @experiment.finished_key => true)
         | 
| 250 | 
            +
                  end
         | 
| 251 | 
            +
             | 
| 252 | 
            +
                  it "passes through options" do
         | 
| 253 | 
            +
                    Split.configuration.experiments[@experiment_name] = {
         | 
| 254 | 
            +
                      :alternatives => @alternatives,
         | 
| 255 | 
            +
                      :metric => :my_metric,
         | 
| 256 | 
            +
                    }
         | 
| 257 | 
            +
                    finished :my_metric, :reset => false
         | 
| 258 | 
            +
                    ab_user.should eql(@experiment.key => @alternative_name, @experiment.finished_key => true)
         | 
| 259 | 
            +
                  end
         | 
| 260 | 
            +
                end
         | 
| 158 261 | 
             
              end
         | 
| 159 262 |  | 
| 160 263 | 
             
              describe 'conversions' do
         | 
| @@ -198,6 +301,7 @@ describe Split::Helper do | |
| 198 301 | 
             
                    (new_red_count + new_blue_count).should eql(previous_red_count + previous_blue_count)
         | 
| 199 302 | 
             
                  end
         | 
| 200 303 | 
             
                end
         | 
| 304 | 
            +
             | 
| 201 305 | 
             
                describe 'finished' do
         | 
| 202 306 | 
             
                  it "should not increment the completed count" do
         | 
| 203 307 | 
             
                    experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| @@ -213,6 +317,7 @@ describe Split::Helper do | |
| 213 317 | 
             
                  end
         | 
| 214 318 | 
             
                end
         | 
| 215 319 | 
             
              end
         | 
| 320 | 
            +
             | 
| 216 321 | 
             
              describe 'when ip address is ignored' do
         | 
| 217 322 | 
             
                before(:each) do
         | 
| 218 323 | 
             
                  @request = OpenStruct.new(:ip => '81.19.48.130')
         | 
| @@ -242,6 +347,7 @@ describe Split::Helper do | |
| 242 347 | 
             
                    (new_red_count + new_blue_count).should eql(previous_red_count + previous_blue_count)
         | 
| 243 348 | 
             
                  end
         | 
| 244 349 | 
             
                end
         | 
| 350 | 
            +
             | 
| 245 351 | 
             
                describe 'finished' do
         | 
| 246 352 | 
             
                  it "should not increment the completed count" do
         | 
| 247 353 | 
             
                    experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| @@ -263,7 +369,7 @@ describe Split::Helper do | |
| 263 369 | 
             
                  experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 264 370 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 265 371 | 
             
                  experiment.version.should eql(0)
         | 
| 266 | 
            -
                   | 
| 372 | 
            +
                  ab_user.should eql({'link_color' => alternative_name})
         | 
| 267 373 | 
             
                end
         | 
| 268 374 |  | 
| 269 375 | 
             
                it "should save the version of the experiment to the session" do
         | 
| @@ -271,7 +377,7 @@ describe Split::Helper do | |
| 271 377 | 
             
                  experiment.reset
         | 
| 272 378 | 
             
                  experiment.version.should eql(1)
         | 
| 273 379 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 274 | 
            -
                   | 
| 380 | 
            +
                  ab_user.should eql({'link_color:1' => alternative_name})
         | 
| 275 381 | 
             
                end
         | 
| 276 382 |  | 
| 277 383 | 
             
                it "should load the experiment even if the version is not 0" do
         | 
| @@ -279,7 +385,7 @@ describe Split::Helper do | |
| 279 385 | 
             
                  experiment.reset
         | 
| 280 386 | 
             
                  experiment.version.should eql(1)
         | 
| 281 387 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 282 | 
            -
                   | 
| 388 | 
            +
                  ab_user.should eql({'link_color:1' => alternative_name})
         | 
| 283 389 | 
             
                  return_alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 284 390 | 
             
                  return_alternative_name.should eql(alternative_name)
         | 
| 285 391 | 
             
                end
         | 
| @@ -287,7 +393,7 @@ describe Split::Helper do | |
| 287 393 | 
             
                it "should reset the session of a user on an older version of the experiment" do
         | 
| 288 394 | 
             
                  experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 289 395 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 290 | 
            -
                   | 
| 396 | 
            +
                  ab_user.should eql({'link_color' => alternative_name})
         | 
| 291 397 | 
             
                  alternative = Split::Alternative.new(alternative_name, 'link_color')
         | 
| 292 398 | 
             
                  alternative.participant_count.should eql(1)
         | 
| 293 399 |  | 
| @@ -297,7 +403,7 @@ describe Split::Helper do | |
| 297 403 | 
             
                  alternative.participant_count.should eql(0)
         | 
| 298 404 |  | 
| 299 405 | 
             
                  new_alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 300 | 
            -
                   | 
| 406 | 
            +
                  ab_user['link_color:1'].should eql(new_alternative_name)
         | 
| 301 407 | 
             
                  new_alternative = Split::Alternative.new(new_alternative_name, 'link_color')
         | 
| 302 408 | 
             
                  new_alternative.participant_count.should eql(1)
         | 
| 303 409 | 
             
                end
         | 
| @@ -305,7 +411,7 @@ describe Split::Helper do | |
| 305 411 | 
             
                it "should cleanup old versions of experiments from the session" do
         | 
| 306 412 | 
             
                  experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 307 413 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 308 | 
            -
                   | 
| 414 | 
            +
                  ab_user.should eql({'link_color' => alternative_name})
         | 
| 309 415 | 
             
                  alternative = Split::Alternative.new(alternative_name, 'link_color')
         | 
| 310 416 | 
             
                  alternative.participant_count.should eql(1)
         | 
| 311 417 |  | 
| @@ -315,13 +421,13 @@ describe Split::Helper do | |
| 315 421 | 
             
                  alternative.participant_count.should eql(0)
         | 
| 316 422 |  | 
| 317 423 | 
             
                  new_alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 318 | 
            -
                   | 
| 424 | 
            +
                  ab_user.should eql({'link_color:1' => new_alternative_name})
         | 
| 319 425 | 
             
                end
         | 
| 320 426 |  | 
| 321 427 | 
             
                it "should only count completion of users on the current version" do
         | 
| 322 428 | 
             
                  experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 323 429 | 
             
                  alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 324 | 
            -
                   | 
| 430 | 
            +
                  ab_user.should eql({'link_color' => alternative_name})
         | 
| 325 431 | 
             
                  alternative = Split::Alternative.new(alternative_name, 'link_color')
         | 
| 326 432 |  | 
| 327 433 | 
             
                  experiment.reset
         | 
| @@ -466,10 +572,109 @@ describe Split::Helper do | |
| 466 572 | 
             
                      finished('link_color')
         | 
| 467 573 | 
             
                    end
         | 
| 468 574 | 
             
                  end
         | 
| 469 | 
            -
             | 
| 470 | 
            -
             | 
| 471 575 | 
             
                end
         | 
| 576 | 
            +
              end
         | 
| 472 577 |  | 
| 578 | 
            +
              context "with preloaded config" do
         | 
| 579 | 
            +
                before { Split.configuration.experiments = {}}
         | 
| 580 | 
            +
              
         | 
| 581 | 
            +
                it "pulls options from config file" do
         | 
| 582 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 583 | 
            +
                    :alternatives => [ "control_opt", "other_opt" ],
         | 
| 584 | 
            +
                  }
         | 
| 585 | 
            +
                  ab_test :my_experiment
         | 
| 586 | 
            +
                  Split::Experiment.find(:my_experiment).alternative_names.should == [ "control_opt", "other_opt" ]
         | 
| 587 | 
            +
                end
         | 
| 588 | 
            +
              
         | 
| 589 | 
            +
                it "can be called multiple times" do
         | 
| 590 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 591 | 
            +
                    :alternatives => [ "control_opt", "other_opt" ],
         | 
| 592 | 
            +
                  }
         | 
| 593 | 
            +
                  5.times { ab_test :my_experiment }
         | 
| 594 | 
            +
                  experiment = Split::Experiment.find(:my_experiment)
         | 
| 595 | 
            +
                  experiment.alternative_names.should == [ "control_opt", "other_opt" ]
         | 
| 596 | 
            +
                  experiment.participant_count.should == 1
         | 
| 597 | 
            +
                end
         | 
| 598 | 
            +
              
         | 
| 599 | 
            +
                it "accepts multiple alternatives" do
         | 
| 600 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 601 | 
            +
                    :alternatives => [ "control_opt", "second_opt", "third_opt" ],
         | 
| 602 | 
            +
                  }
         | 
| 603 | 
            +
                  ab_test :my_experiment
         | 
| 604 | 
            +
                  experiment = Split::Experiment.find(:my_experiment)
         | 
| 605 | 
            +
                  experiment.alternative_names.should == [ "control_opt", "second_opt", "third_opt" ]
         | 
| 606 | 
            +
                end
         | 
| 607 | 
            +
              
         | 
| 608 | 
            +
                it "accepts probability on alternatives" do
         | 
| 609 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 610 | 
            +
                    :alternatives => [
         | 
| 611 | 
            +
                      { :name => "control_opt", :percent => 67 },
         | 
| 612 | 
            +
                      { :name => "second_opt", :percent => 10 },
         | 
| 613 | 
            +
                      { :name => "third_opt", :percent => 23 },
         | 
| 614 | 
            +
                    ],
         | 
| 615 | 
            +
                  }
         | 
| 616 | 
            +
                  ab_test :my_experiment
         | 
| 617 | 
            +
                  experiment = Split::Experiment.find(:my_experiment)
         | 
| 618 | 
            +
                  experiment.alternatives.collect{|a| [a.name, a.weight]}.should == [['control_opt', 0.67], ['second_opt', 0.1], ['third_opt', 0.23]]
         | 
| 619 | 
            +
                  
         | 
| 620 | 
            +
                end
         | 
| 621 | 
            +
              
         | 
| 622 | 
            +
                it "accepts probability on some alternatives" do
         | 
| 623 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 624 | 
            +
                    :alternatives => [
         | 
| 625 | 
            +
                      { :name => "control_opt", :percent => 34 },
         | 
| 626 | 
            +
                      "second_opt",
         | 
| 627 | 
            +
                      { :name => "third_opt", :percent => 23 },
         | 
| 628 | 
            +
                      "fourth_opt",
         | 
| 629 | 
            +
                    ],
         | 
| 630 | 
            +
                  }
         | 
| 631 | 
            +
                  ab_test :my_experiment
         | 
| 632 | 
            +
                  experiment = Split::Experiment.find(:my_experiment)
         | 
| 633 | 
            +
                  names_and_weights = experiment.alternatives.collect{|a| [a.name, a.weight]}
         | 
| 634 | 
            +
                  names_and_weights.should == [['control_opt', 0.34], ['second_opt', 0.215], ['third_opt', 0.23], ['fourth_opt', 0.215]]
         | 
| 635 | 
            +
                  names_and_weights.inject(0){|sum, nw| sum + nw[1]}.should == 1.0
         | 
| 636 | 
            +
                end
         | 
| 637 | 
            +
              
         | 
| 638 | 
            +
                it "allows name param without probability" do
         | 
| 639 | 
            +
                  Split.configuration.experiments[:my_experiment] = {
         | 
| 640 | 
            +
                    :alternatives => [
         | 
| 641 | 
            +
                      { :name => "control_opt" },
         | 
| 642 | 
            +
                      "second_opt",
         | 
| 643 | 
            +
                      { :name => "third_opt", :percent => 64 },
         | 
| 644 | 
            +
                    ],
         | 
| 645 | 
            +
                  }
         | 
| 646 | 
            +
                  ab_test :my_experiment
         | 
| 647 | 
            +
                  experiment = Split::Experiment.find(:my_experiment)
         | 
| 648 | 
            +
                  names_and_weights = experiment.alternatives.collect{|a| [a.name, a.weight]}
         | 
| 649 | 
            +
                  names_and_weights.should ==  [['control_opt', 0.18], ['second_opt', 0.18], ['third_opt', 0.64]]
         | 
| 650 | 
            +
                  names_and_weights.inject(0){|sum, nw| sum + nw[1]}.should == 1.0
         | 
| 651 | 
            +
                end
         | 
| 652 | 
            +
             | 
| 653 | 
            +
                it "fails gracefully if config is missing experiment" do
         | 
| 654 | 
            +
                  Split.configuration.experiments = { :other_experiment => { :foo => "Bar" } }
         | 
| 655 | 
            +
                  lambda { ab_test :my_experiment }.should raise_error(/not found/i)
         | 
| 656 | 
            +
                end
         | 
| 657 | 
            +
             | 
| 658 | 
            +
                it "fails gracefully if config is missing" do
         | 
| 659 | 
            +
                  Split.configuration.experiments = nil
         | 
| 660 | 
            +
                  lambda { ab_test :my_experiment }.should raise_error(/not found/i)
         | 
| 661 | 
            +
                end
         | 
| 662 | 
            +
             | 
| 663 | 
            +
                it "fails gracefully if config is missing alternatives" do
         | 
| 664 | 
            +
                  Split.configuration.experiments[:my_experiment] = { :foo => "Bar" }
         | 
| 665 | 
            +
                  lambda { ab_test :my_experiment }.should raise_error(/alternatives/i)
         | 
| 666 | 
            +
                end
         | 
| 473 667 | 
             
              end
         | 
| 474 668 |  | 
| 669 | 
            +
              it 'should handle multiple experiments correctly' do
         | 
| 670 | 
            +
                experiment = Split::Experiment.find_or_create('link_color', 'blue', 'red')
         | 
| 671 | 
            +
                experiment2 = Split::Experiment.find_or_create('link_color2', 'blue', 'red')
         | 
| 672 | 
            +
                alternative_name = ab_test('link_color', 'blue', 'red')
         | 
| 673 | 
            +
                alternative_name2 = ab_test('link_color2', 'blue', 'red')
         | 
| 674 | 
            +
                finished('link_color2')
         | 
| 675 | 
            +
             | 
| 676 | 
            +
                experiment2.alternatives.each do |alt|
         | 
| 677 | 
            +
                  alt.unfinished_count.should eq(0)
         | 
| 678 | 
            +
                end
         | 
| 679 | 
            +
              end
         | 
| 475 680 | 
             
            end
         | 
    
        data/spec/metric_spec.rb
    ADDED
    
    | @@ -0,0 +1,30 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
            require 'split/metric'
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            describe Split::Metric do
         | 
| 5 | 
            +
              describe 'possible experiments' do
         | 
| 6 | 
            +
                it "should load the experiment if there is one, but no metric" do
         | 
| 7 | 
            +
                  experiment = Split::Experiment.find_or_create('color', 'red', 'blue')
         | 
| 8 | 
            +
                  Split::Metric.possible_experiments('color').should == [experiment]
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                it "should load the experiments in a metric" do
         | 
| 12 | 
            +
                  experiment1 = Split::Experiment.find_or_create('color', 'red', 'blue')
         | 
| 13 | 
            +
                  experiment2 = Split::Experiment.find_or_create('size', 'big', 'small')
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                  metric = Split::Metric.new(:name => 'purchase', :experiments => [experiment1, experiment2])
         | 
| 16 | 
            +
                  metric.save
         | 
| 17 | 
            +
                  Split::Metric.possible_experiments('purchase').should include(experiment1, experiment2)
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                it "should load both the metric experiments and an experiment with the same name" do
         | 
| 21 | 
            +
                  experiment1 = Split::Experiment.find_or_create('purchase', 'red', 'blue')
         | 
| 22 | 
            +
                  experiment2 = Split::Experiment.find_or_create('size', 'big', 'small')
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                  metric = Split::Metric.new(:name => 'purchase', :experiments => [experiment2])
         | 
| 25 | 
            +
                  metric.save
         | 
| 26 | 
            +
                  Split::Metric.possible_experiments('purchase').should include(experiment1, experiment2)
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
              end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            end
         | 
| @@ -0,0 +1,31 @@ | |
| 1 | 
            +
            require "spec_helper"
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Split::Persistence::CookieAdapter do
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              let(:context) { mock(:cookies => CookiesMock.new) }
         | 
| 6 | 
            +
              subject { Split::Persistence::CookieAdapter.new(context) }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
              describe "#[] and #[]=" do
         | 
| 9 | 
            +
                it "should set and return the value for given key" do
         | 
| 10 | 
            +
                  subject["my_key"] = "my_value"
         | 
| 11 | 
            +
                  subject["my_key"].should eq("my_value")
         | 
| 12 | 
            +
                end
         | 
| 13 | 
            +
              end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
              describe "#delete" do
         | 
| 16 | 
            +
                it "should delete the given key" do
         | 
| 17 | 
            +
                  subject["my_key"] = "my_value"
         | 
| 18 | 
            +
                  subject.delete("my_key")
         | 
| 19 | 
            +
                  subject["my_key"].should be_nil
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
              end
         | 
| 22 | 
            +
             | 
| 23 | 
            +
              describe "#keys" do
         | 
| 24 | 
            +
                it "should return an array of the session's stored keys" do
         | 
| 25 | 
            +
                  subject["my_key"] = "my_value"
         | 
| 26 | 
            +
                  subject["my_second_key"] = "my_second_value"
         | 
| 27 | 
            +
                  subject.keys.should =~ ["my_key", "my_second_key"]
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
              end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            end
         |