Skip to content

Instantly share code, notes, and snippets.

@joshed-io
Created July 14, 2011 01:16
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save joshed-io/1081685 to your computer and use it in GitHub Desktop.
Save joshed-io/1081685 to your computer and use it in GitHub Desktop.
Easiest possible RSpec Performance Test w/ Scenarios
# here's a quick recipe to run a performance test
# with this method, you can:
#-> easily choose the examples you want included from your existing specs
#-> define the target number of total iterations you'd like, no limit
#-> tune the transaction mix by specifying frequency metadata for each example
#-> be happy that the transaction mix is fairly homogeneous over the test interval
# (ideally you'd run this with acceptance (webrat/capybara) specs, but you
# could employ this technique for any rspec test)
# note: there's no measuring or performance reporting here, but you can capture
# it all in New Relic (enable monitor_mode) and run a time-based report when
# your test is complete.
# First, THE DSL
# From your test suite, pick a subset of examples useful for
# generating load, and assign a perf key and optionally a 'frequency' of execution
it "should be fast", :perf => true, :frequency => 0.8 do
visit "/home" #using capybara syntax for this example
page.should have_content("i'm ready already!")
end
# now, it's time to make rspec aware of these values
#somewhere in your spec_helper.rb, place this:
RSpec.configure do |config|
#only run examples with 'perf' specified in metadata
config.filter_run(:perf => true)
#if your performance scenario calls for 'wait time', do it here. e.g.
config.after(:each) do
#sleep(example.metadata[:wait_time])
end
end
# note that you'll want to include this block when you want to run a
# performance test, so you might create a separate RAILS/RACK_ENV or
# conditionally include the block based on an environment variable
# ----------------------
#now for the main RSpec patch - no guarantees and I'm sure there's
#a better outpatient way to do this. but for now, scapel!
#copy this anywhere code is required after Rspec is loaded,
#your spec_helper for example
#this is helper to figure out example amount + frequency
#examples without frequency specified default to 1
def calculate_iterations(options)
return 0 unless options[:perf] #filtering here improves performance
iter_ct = (ENV['ITER'] ? ENV['ITER'].to_i : 1000)
(((iter_ct.to_f * (options[:frequency] || 1.0))+1).to_i)
end
#patch rspec example accumulation to adjust example
#levels to the right amount and frequency
### begin derdy patch, helmet required ###
module RSpec
module Core
class ExampleGroup
def self.define_example_method(name, extra_options={})
module_eval(<<-END_RUBY, __FILE__, __LINE__)
def self.#{name}(desc=nil, *args, &block)
options = build_metadata_hash_from(args)
options.update(:pending => true) unless block
options.update(#{extra_options.inspect})
calculate_iterations(options).times do
examples << RSpec::Core::Example.new(self, desc, options, block)
end
#shuffle examples for good perf simulation
examples.shuffle!
examples.last
end
END_RUBY
end
define_example_method :example
class << self
alias_method :alias_example_to, :define_example_method
end
alias_example_to :it
alias_example_to :specify
alias_example_to :focused, :focused => true, :focus => true
alias_example_to :focus, :focused => true, :focus => true
alias_example_to :pending, :pending => true
alias_example_to :xit, :pending => true
end
end
end
# -----------
# that's it for the rubies.
# To run: Specify the TOTAL number of example executions
# you'd like as an environment variable
# ** from your shell **
# ITER=1000 bundle exec rake spec:acceptance #or whatever your test command is
# *******************
# the ITER is multiplied with frequency from metadata to determine
# how many of each example get run. e.g. the example
it "should run a few times", :perf => true, :frequency => 0.5 do ... end
# paired with the command
# ITER=100 bundle exec rake spec:acceptance
# will run ITER*frequency times, or 50 in this case. The number of iterations
# of each example is calculated independently, so you can easily go over
# (i.e. its up to you to make sure your frequencies add up to 1.0)
# to scale up your load, you'll have to start more RSpec processes, or
# run this simultaneously across machines. As long as your examples are
# idempotent (and they are, right?), this shouldn't be a problem.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment