vanity 1.9.3 → 2.0.0.beta

Sign up to get free protection for your applications and to get access to all the features.
Files changed (54) hide show
  1. data/.gitignore +0 -1
  2. data/.travis.yml +2 -53
  3. data/Appraisals +3 -12
  4. data/CHANGELOG +1 -5
  5. data/Gemfile +0 -10
  6. data/Gemfile.lock +3 -38
  7. data/README.rdoc +2 -13
  8. data/Rakefile +7 -13
  9. data/bin/vanity +0 -1
  10. data/doc/rails.textile +0 -12
  11. data/gemfiles/rails32.gemfile +2 -4
  12. data/gemfiles/rails32.gemfile.lock +6 -14
  13. data/gemfiles/rails4.gemfile +1 -4
  14. data/gemfiles/rails4.gemfile.lock +5 -15
  15. data/lib/vanity/adapters/active_record_adapter.rb +14 -10
  16. data/lib/vanity/experiment/ab_test.rb +1 -1
  17. data/lib/vanity/frameworks/rails.rb +11 -17
  18. data/lib/vanity/frameworks.rb +3 -10
  19. data/lib/vanity/metric/active_record.rb +20 -19
  20. data/lib/vanity/playground.rb +1 -3
  21. data/lib/vanity/version.rb +1 -1
  22. data/test/adapters/redis_adapter_test.rb +26 -27
  23. data/test/autoconnect_test.rb +19 -17
  24. data/test/cli_test.rb +19 -26
  25. data/test/experiment/ab_test.rb +2 -15
  26. data/test/experiment/base_test.rb +18 -22
  27. data/test/frameworks/rails/action_controller_test.rb +184 -0
  28. data/test/frameworks/rails/action_mailer_test.rb +65 -0
  29. data/test/{rails_helper_test.rb → frameworks/rails/action_view_test.rb} +1 -1
  30. data/test/frameworks/rails/rails_test.rb +285 -0
  31. data/test/helper_test.rb +13 -10
  32. data/test/metric/active_record_test.rb +50 -66
  33. data/test/metric/base_test.rb +39 -41
  34. data/test/metric/google_analytics_test.rb +13 -16
  35. data/test/metric/remote_test.rb +18 -19
  36. data/test/playground_test.rb +1 -1
  37. data/test/test_helper.rb +25 -43
  38. data/test/{rails_dashboard_test.rb → web/rails/dashboard_test.rb} +2 -1
  39. data/vanity.gemspec +3 -2
  40. metadata +33 -33
  41. data/gemfiles/rails3.gemfile +0 -32
  42. data/gemfiles/rails3.gemfile.lock +0 -172
  43. data/gemfiles/rails31.gemfile +0 -32
  44. data/gemfiles/rails31.gemfile.lock +0 -181
  45. data/generators/templates/vanity_migration.rb +0 -54
  46. data/generators/vanity_generator.rb +0 -8
  47. data/test/myapp/app/controllers/application_controller.rb +0 -2
  48. data/test/myapp/app/controllers/main_controller.rb +0 -7
  49. data/test/myapp/config/boot.rb +0 -110
  50. data/test/myapp/config/environment.rb +0 -10
  51. data/test/myapp/config/environments/production.rb +0 -0
  52. data/test/myapp/config/routes.rb +0 -3
  53. data/test/passenger_test.rb +0 -52
  54. data/test/rails_test.rb +0 -554
@@ -0,0 +1,184 @@
1
+ require "test_helper"
2
+
3
+ class UseVanityController < ActionController::Base
4
+ attr_accessor :current_user
5
+
6
+ def index
7
+ render :text=>ab_test(:pie_or_cake)
8
+ end
9
+
10
+ def js
11
+ ab_test(:pie_or_cake)
12
+ render :inline => "<%= vanity_js -%>"
13
+ end
14
+ end
15
+
16
+ # Pages accessible to everyone, e.g. sign in, community search.
17
+ class UseVanityControllerTest < ActionController::TestCase
18
+ tests UseVanityController
19
+
20
+ def setup
21
+ super
22
+ metric :sugar_high
23
+ new_ab_test :pie_or_cake do
24
+ metrics :sugar_high
25
+ end
26
+ UseVanityController.class_eval do
27
+ use_vanity :current_user
28
+ end
29
+ if ::Rails.respond_to?(:application) # Rails 3 configuration
30
+ ::Rails.application.config.session_options[:domain] = '.foo.bar'
31
+ end
32
+ end
33
+
34
+ def test_render_js_for_tests
35
+ Vanity.playground.use_js!
36
+ get :js
37
+ assert_match /script.*e=pie_or_cake.*script/m, @response.body
38
+ end
39
+
40
+ def test_chooses_sets_alternatives_for_rails_tests
41
+ experiment(:pie_or_cake).chooses(true)
42
+ get :index
43
+ assert_equal 'true', @response.body
44
+
45
+ experiment(:pie_or_cake).chooses(false)
46
+ get :index
47
+ assert_equal 'false', @response.body
48
+ end
49
+
50
+ def test_adds_participant_to_experiment
51
+ get :index
52
+ assert_equal 1, experiment(:pie_or_cake).alternatives.map(&:participants).sum
53
+ end
54
+
55
+ def test_does_not_add_invalid_participant_to_experiment
56
+ @request.user_agent = "Googlebot/2.1 ( http://www.google.com/bot.html)"
57
+ get :index
58
+ assert_equal 0, experiment(:pie_or_cake).alternatives.map(&:participants).sum
59
+ end
60
+
61
+ def test_vanity_cookie_is_persistent
62
+ get :index
63
+ cookie = @response["Set-Cookie"].to_s
64
+ assert_match /vanity_id=[a-f0-9]{32};/, cookie
65
+ expires = cookie[/expires=(.*)(;|$)/, 1]
66
+ assert expires
67
+ assert_in_delta Time.parse(expires), Time.now + 1.month, 1.day
68
+ end
69
+
70
+ def test_vanity_cookie_default_id
71
+ get :index
72
+ assert cookies["vanity_id"] =~ /^[a-f0-9]{32}$/
73
+ end
74
+
75
+ def test_vanity_cookie_retains_id
76
+ @request.cookies["vanity_id"] = "from_last_time"
77
+ get :index
78
+ assert_equal "from_last_time", cookies["vanity_id"]
79
+ end
80
+
81
+ def test_vanity_identity_set_from_cookie
82
+ @request.cookies["vanity_id"] = "from_last_time"
83
+ get :index
84
+ assert_equal "from_last_time", @controller.send(:vanity_identity)
85
+ end
86
+
87
+ def test_vanity_identity_set_from_user
88
+ @controller.current_user = mock("user", :id=>"user_id")
89
+ get :index
90
+ assert_equal "user_id", @controller.send(:vanity_identity)
91
+ end
92
+
93
+ def test_vanity_identity_with_no_user_model
94
+ UseVanityController.class_eval do
95
+ use_vanity nil
96
+ end
97
+ @controller.current_user = Object.new
98
+ get :index
99
+ assert cookies["vanity_id"] =~ /^[a-f0-9]{32}$/
100
+ end
101
+
102
+ def test_vanity_identity_set_with_block
103
+ UseVanityController.class_eval do
104
+ attr_accessor :project_id
105
+ use_vanity { |controller| controller.project_id }
106
+ end
107
+ @controller.project_id = "576"
108
+ get :index
109
+ assert_equal "576", @controller.send(:vanity_identity)
110
+ end
111
+
112
+ def test_vanity_identity_set_with_indentity_paramater
113
+ get :index, :_identity => "id_from_params"
114
+ assert_equal "id_from_params", @controller.send(:vanity_identity)
115
+ end
116
+
117
+ def test_vanity_identity_prefers_block_over_symbol
118
+ UseVanityController.class_eval do
119
+ attr_accessor :project_id
120
+ use_vanity(:current_user) { |controller| controller.project_id }
121
+ end
122
+ @controller.project_id = "576"
123
+ @controller.current_user = stub(:id=>"user_id")
124
+
125
+ get :index
126
+ assert_equal "576", @controller.send(:vanity_identity)
127
+ end
128
+
129
+ def test_vanity_identity_prefers_parameter_over_cookie
130
+ @request.cookies['vanity_id'] = "old_id"
131
+ get :index, :_identity => "id_from_params"
132
+ assert_equal "id_from_params", @controller.send(:vanity_identity)
133
+ assert cookies['vanity_id'], "id_from_params"
134
+ end
135
+
136
+ def test_vanity_identity_prefers_cookie_over_object
137
+ @request.cookies['vanity_id'] = "from_last_time"
138
+ @controller.current_user = stub(:id=>"user_id")
139
+ get :index
140
+ assert_equal "from_last_time", @controller.send(:vanity_identity)
141
+ end
142
+
143
+ # query parameter filter
144
+
145
+ def test_redirects_and_loses_vanity_query_parameter
146
+ get :index, :foo=>"bar", :_vanity=>"567"
147
+ assert_redirected_to "/use_vanity?foo=bar"
148
+ end
149
+
150
+ def test_sets_choices_from_vanity_query_parameter
151
+ first = experiment(:pie_or_cake).alternatives.first
152
+ fingerprint = experiment(:pie_or_cake).fingerprint(first)
153
+ 10.times do
154
+ @controller = nil ; setup_controller_request_and_response
155
+ get :index, :_vanity => fingerprint
156
+ assert_equal experiment(:pie_or_cake).choose, experiment(:pie_or_cake).alternatives.first
157
+ assert experiment(:pie_or_cake).showing?(first)
158
+ end
159
+ end
160
+
161
+ def test_does_nothing_with_vanity_query_parameter_for_posts
162
+ experiment(:pie_or_cake).chooses(experiment(:pie_or_cake).alternatives.last.value)
163
+ first = experiment(:pie_or_cake).alternatives.first
164
+ fingerprint = experiment(:pie_or_cake).fingerprint(first)
165
+ post :index, :foo => "bar", :_vanity => fingerprint
166
+ assert_response :success
167
+ assert !experiment(:pie_or_cake).showing?(first)
168
+ end
169
+
170
+ def test_track_param_tracks_a_metric
171
+ get :index, :_identity => "123", :_track => "sugar_high"
172
+ assert_equal experiment(:pie_or_cake).alternatives[0].converted, 1
173
+ end
174
+
175
+ def test_cookie_domain_from_rails_configuration
176
+ get :index
177
+ assert_match /domain=.foo.bar/, @response["Set-Cookie"] if ::Rails.respond_to?(:application)
178
+ end
179
+
180
+ def teardown
181
+ super
182
+ end
183
+
184
+ end
@@ -0,0 +1,65 @@
1
+ require "test_helper"
2
+
3
+ class VanityMailer < ActionMailer::Base
4
+ include Vanity::Rails::Helpers
5
+ include ActionView::Helpers::AssetTagHelper
6
+ include ActionView::Helpers::TagHelper
7
+
8
+ def ab_test_subject(user, forced_outcome=true)
9
+ use_vanity_mailer user
10
+ experiment(:pie_or_cake).chooses(forced_outcome)
11
+
12
+ if defined?(Rails::Railtie)
13
+ mail :subject =>ab_test(:pie_or_cake).to_s, :body => ""
14
+ else
15
+ subject ab_test(:pie_or_cake).to_s
16
+ body ""
17
+ end
18
+ end
19
+
20
+ def ab_test_content(user)
21
+ use_vanity_mailer user
22
+
23
+ if defined?(Rails::Railtie)
24
+ mail do |format|
25
+ format.html { render :text=>view_context.vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000") }
26
+ end
27
+ else
28
+ body vanity_tracking_image(Vanity.context.vanity_identity, :open, :host => "127.0.0.1:3000")
29
+ end
30
+ end
31
+ end
32
+
33
+ class UseVanityMailerTest < ActionMailer::TestCase
34
+ tests VanityMailer
35
+
36
+ def setup
37
+ super
38
+ metric :sugar_high
39
+ new_ab_test :pie_or_cake do
40
+ metrics :sugar_high
41
+ end
42
+ end
43
+
44
+ def test_js_enabled_still_adds_participant
45
+ Vanity.playground.use_js!
46
+ VanityMailer.ab_test_subject(nil, true)
47
+
48
+ alts = experiment(:pie_or_cake).alternatives
49
+ assert_equal 1, alts.map(&:participants).sum
50
+ end
51
+
52
+ def test_returns_different_alternatives
53
+ email = VanityMailer.ab_test_subject(nil, true)
54
+ assert_equal 'true', email.subject
55
+
56
+ email = VanityMailer.ab_test_subject(nil, false)
57
+ assert_equal 'false', email.subject
58
+ end
59
+
60
+ def test_tracking_image_is_rendered
61
+ email = VanityMailer.ab_test_content(nil)
62
+ assert email.body =~ /<img/
63
+ assert email.body =~ /_identity=/
64
+ end
65
+ end
@@ -1,4 +1,4 @@
1
- require "test/test_helper"
1
+ require "test_helper"
2
2
 
3
3
  class RailsHelperTest < ActionView::TestCase
4
4
  include Vanity::Rails::Helpers
@@ -0,0 +1,285 @@
1
+ require "test_helper"
2
+
3
+ class LoadPathAndConnectionConfigurationTest < Test::Unit::TestCase
4
+
5
+ def test_load_path
6
+ assert_equal File.expand_path("tmp/experiments"), load_rails("", <<-RB)
7
+ $stdout << Vanity.playground.load_path
8
+ RB
9
+ end
10
+
11
+ def test_settable_load_path
12
+ assert_equal File.expand_path("tmp/predictions"), load_rails(%Q{\nVanity.playground.load_path = "predictions"\n}, <<-RB)
13
+ $stdout << Vanity.playground.load_path
14
+ RB
15
+ end
16
+
17
+ def test_absolute_load_path
18
+ Dir.mktmpdir do |dir|
19
+ assert_equal dir, load_rails(%Q{\nVanity.playground.load_path = "#{dir}"\n}, <<-RB)
20
+ $stdout << Vanity.playground.load_path
21
+ RB
22
+ end
23
+ end
24
+
25
+ if ENV['DB'] == 'redis'
26
+ def test_default_connection
27
+ assert_equal "redis://127.0.0.1:6379/0", load_rails("", <<-RB)
28
+ $stdout << Vanity.playground.connection
29
+ RB
30
+ end
31
+
32
+ def test_connection_from_string
33
+ assert_equal "redis://192.168.1.1:6379/5", load_rails(%Q{\nVanity.playground.establish_connection "redis://192.168.1.1:6379/5"\n}, <<-RB)
34
+ $stdout << Vanity.playground.connection
35
+ RB
36
+ end
37
+
38
+ def test_connection_from_yaml
39
+ FileUtils.mkpath "tmp/config"
40
+ @original_env = ENV["RAILS_ENV"]
41
+ ENV["RAILS_ENV"] = "production"
42
+ File.open("tmp/config/vanity.yml", "w") do |io|
43
+ io.write <<-YML
44
+ production:
45
+ adapter: redis
46
+ host: somehost
47
+ database: 15
48
+ YML
49
+ end
50
+ assert_equal "redis://somehost:6379/15", load_rails("", <<-RB)
51
+ $stdout << Vanity.playground.connection
52
+ RB
53
+ ensure
54
+ ENV["RAILS_ENV"] = @original_env
55
+ File.unlink "tmp/config/vanity.yml"
56
+ end
57
+
58
+ def test_connection_from_yaml_url
59
+ FileUtils.mkpath "tmp/config"
60
+ @original_env = ENV["RAILS_ENV"]
61
+ ENV["RAILS_ENV"] = "production"
62
+ File.open("tmp/config/vanity.yml", "w") do |io|
63
+ io.write <<-YML
64
+ production: redis://somehost/15
65
+ YML
66
+ end
67
+ assert_equal "redis://somehost:6379/15", load_rails("", <<-RB)
68
+ $stdout << Vanity.playground.connection
69
+ RB
70
+ ensure
71
+ ENV["RAILS_ENV"] = @original_env
72
+ File.unlink "tmp/config/vanity.yml"
73
+ end
74
+
75
+ def test_connection_from_yaml_with_erb
76
+ FileUtils.mkpath "tmp/config"
77
+ @original_env = ENV["RAILS_ENV"]
78
+ ENV["RAILS_ENV"] = "production"
79
+ # Pass storage URL through environment like heroku does
80
+ @original_redis_url = ENV["REDIS_URL"]
81
+ ENV["REDIS_URL"] = "redis://somehost:6379/15"
82
+ File.open("tmp/config/vanity.yml", "w") do |io|
83
+ io.write <<-YML
84
+ production: <%= ENV['REDIS_URL'] %>
85
+ YML
86
+ end
87
+ assert_equal "redis://somehost:6379/15", load_rails("", <<-RB)
88
+ $stdout << Vanity.playground.connection
89
+ RB
90
+ ensure
91
+ ENV["RAILS_ENV"] = @original_env
92
+ ENV["REDIS_URL"] = @original_redis_url
93
+ File.unlink "tmp/config/vanity.yml"
94
+ end
95
+
96
+ def test_connection_from_redis_yml
97
+ FileUtils.mkpath "tmp/config"
98
+ yml = File.open("tmp/config/redis.yml", "w")
99
+ yml << "production: internal.local:6379\n"
100
+ yml.flush
101
+ assert_equal "redis://internal.local:6379/0", load_rails("", <<-RB)
102
+ $stdout << Vanity.playground.connection
103
+ RB
104
+ ensure
105
+ File.unlink yml.path
106
+ end
107
+ end
108
+
109
+ if ENV['DB'] == 'mongo'
110
+ def test_mongo_connection_from_yaml
111
+ FileUtils.mkpath "tmp/config"
112
+ File.open("tmp/config/vanity.yml", "w") do |io|
113
+ io.write <<-YML
114
+ mongodb:
115
+ adapter: mongodb
116
+ host: localhost
117
+ port: 27017
118
+ database: vanity_test
119
+ YML
120
+ end
121
+
122
+ assert_equal "mongodb://localhost:27017/vanity_test", load_rails("", <<-RB, "mongodb")
123
+ $stdout << Vanity.playground.connection
124
+ RB
125
+ ensure
126
+ File.unlink "tmp/config/vanity.yml"
127
+ end
128
+
129
+ unless ENV['CI'] == 'true' #TODO this doesn't get tested on CI
130
+ def test_mongodb_replica_set_connection
131
+ FileUtils.mkpath "tmp/config"
132
+ File.open("tmp/config/vanity.yml", "w") do |io|
133
+ io.write <<-YML
134
+ mongodb:
135
+ adapter: mongodb
136
+ hosts:
137
+ - localhost
138
+ port: 27017
139
+ database: vanity_test
140
+ YML
141
+ end
142
+
143
+ assert_equal "mongodb://localhost:27017/vanity_test", load_rails("", <<-RB, "mongodb")
144
+ $stdout << Vanity.playground.connection
145
+ RB
146
+
147
+ assert_equal "Mongo::ReplSetConnection", load_rails("", <<-RB, "mongodb")
148
+ $stdout << Vanity.playground.connection.mongo.class
149
+ RB
150
+ ensure
151
+ File.unlink "tmp/config/vanity.yml"
152
+ end
153
+ end
154
+ end
155
+
156
+ def test_connection_from_yaml_missing
157
+ FileUtils.mkpath "tmp/config"
158
+ File.open("tmp/config/vanity.yml", "w") do |io|
159
+ io.write <<-YML
160
+ production:
161
+ adapter: redis
162
+ YML
163
+ end
164
+
165
+ assert_equal "No configuration for development", load_rails("\nbegin\n", <<-RB, "development")
166
+ rescue RuntimeError => e
167
+ $stdout << e.message
168
+ end
169
+ RB
170
+ ensure
171
+ File.unlink "tmp/config/vanity.yml"
172
+ end
173
+
174
+ def test_collection_from_vanity_yaml
175
+ FileUtils.mkpath "tmp/config"
176
+ File.open("tmp/config/vanity.yml", "w") do |io|
177
+ io.write <<-YML
178
+ production:
179
+ collecting: false
180
+ adapter: mock
181
+ YML
182
+ end
183
+ assert_equal "false", load_rails("", <<-RB)
184
+ $stdout << Vanity.playground.collecting?
185
+ RB
186
+ ensure
187
+ File.unlink "tmp/config/vanity.yml"
188
+ end
189
+
190
+ def test_collection_true_in_production_by_default
191
+ assert_equal "true", load_rails("", <<-RB)
192
+ $stdout << Vanity.playground.collecting?
193
+ RB
194
+ end
195
+
196
+ def test_collection_false_in_production_when_configured
197
+ assert_equal "false", load_rails("\nVanity.playground.collecting = false\n", <<-RB)
198
+ $stdout << Vanity.playground.collecting?
199
+ RB
200
+ end
201
+
202
+ def test_collection_true_in_development_by_default
203
+ assert_equal "true", load_rails("", <<-RB, "development")
204
+ $stdout << Vanity.playground.collecting?
205
+ RB
206
+ end
207
+
208
+ def test_collection_true_in_development_when_configured
209
+ assert_equal "true", load_rails("\nVanity.playground.collecting = true\n", <<-RB, "development")
210
+ $stdout << Vanity.playground.collecting?
211
+ RB
212
+ end
213
+
214
+ def test_collection_false_after_test!
215
+ assert_equal "false", load_rails("", <<-RB)
216
+ Vanity.playground.test!
217
+ $stdout << Vanity.playground.collecting?
218
+ RB
219
+ end
220
+
221
+ def test_playground_loads_if_connected
222
+ assert_equal "{}", load_rails("", <<-RB)
223
+ $stdout << Vanity.playground.instance_variable_get(:@experiments).inspect
224
+ RB
225
+ end
226
+
227
+ def test_playground_does_not_load_if_not_connected
228
+ ENV['VANITY_DISABLED'] = '1'
229
+ assert_equal "nil", load_rails("", <<-RB)
230
+ $stdout << Vanity.playground.instance_variable_get(:@experiments).inspect
231
+ RB
232
+ ENV['VANITY_DISABLED'] = nil
233
+ end
234
+
235
+ def load_rails(before_initialize, after_initialize, env="production")
236
+ tmp = Tempfile.open("test.rb")
237
+ begin
238
+ code_setup = <<-RB
239
+ $:.delete_if { |path| path[/gems\\/vanity-\\d/] }
240
+ $:.unshift File.expand_path("../lib")
241
+ RAILS_ROOT = File.expand_path(".")
242
+ RB
243
+ code = code_setup
244
+ code += load_rails_3_or_4(env)
245
+ code += %Q{\nrequire "vanity"\n}
246
+ code += before_initialize
247
+ code += initialize_rails_3_or_4
248
+ code += after_initialize
249
+ tmp.write code
250
+ tmp.flush
251
+ Dir.chdir "tmp" do
252
+ open("| ruby #{tmp.path}").read
253
+ end
254
+ ensure
255
+ tmp.close!
256
+ end
257
+ end
258
+
259
+ def load_rails_3_or_4(env)
260
+ <<-RB
261
+ ENV['BUNDLE_GEMFILE'] ||= "#{ENV['BUNDLE_GEMFILE']}"
262
+ require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
263
+ ENV['RAILS_ENV'] = ENV['RACK_ENV'] = "#{env}"
264
+ require "active_model/railtie"
265
+ require "action_controller/railtie"
266
+
267
+ Bundler.require(:default)
268
+
269
+ module Foo
270
+ class Application < Rails::Application
271
+ config.active_support.deprecation = :notify
272
+ config.eager_load = #{env == "production"} if Rails::Application.respond_to?(:eager_load!)
273
+ ActiveSupport::Deprecation.silenced = true if ActiveSupport::Deprecation.respond_to?(:silenced) && ENV['CI']
274
+ end
275
+ end
276
+ RB
277
+ end
278
+
279
+ def initialize_rails_3_or_4
280
+ <<-RB
281
+ Foo::Application.initialize!
282
+ RB
283
+ end
284
+
285
+ end
data/test/helper_test.rb CHANGED
@@ -1,14 +1,17 @@
1
- require "test/test_helper"
1
+ require "test_helper"
2
2
 
3
- context "Object#track!" do
4
- test "identity option sets identity" do
5
- metric "Coolness"
6
- new_ab_test :foobar do
7
- alternatives "foo", "bar"
8
- metrics :coolness
9
- end
10
- track! :coolness, :identity=>'quux', :values=>[2]
3
+ describe Object do
4
+ describe "#track!" do
5
+ it "identity option sets identity" do
6
+ metric "Coolness"
7
+ new_ab_test :foobar do
8
+ alternatives "foo", "bar"
9
+ metrics :coolness
10
+ end
11
+ track! :coolness, :identity=>'quux', :values=>[2]
11
12
 
12
- assert_equal 2, experiment(:foobar).alternatives.sum(&:conversions)
13
+ # experiment(:foobar).alternatives.sum(&:conversions).must_equal 2
14
+ assert_equal 2, experiment(:foobar).alternatives.sum(&:conversions)
15
+ end
13
16
  end
14
17
  end