Speedy TDD with Rails
Upcoming SlideShare
Loading in...5
×
 

Speedy TDD with Rails

on

  • 5,987 views

Slides from my presentation at ShRUG (Sheffield Ruby User Group) January 2012 http://shrug.org/meetings/shrug-26/

Slides from my presentation at ShRUG (Sheffield Ruby User Group) January 2012 http://shrug.org/meetings/shrug-26/

Statistics

Views

Total Views
5,987
Views on SlideShare
3,476
Embed Views
2,511

Actions

Likes
4
Downloads
25
Comments
2

9 Embeds 2,511

http://blog.patchspace.co.uk 2476
http://feeds.feedburner.com 18
http://ranksit.com 9
http://translate.googleusercontent.com 2
http://www.linkedin.com 2
http://a0.twimg.com 1
http://www.hanrss.com 1
http://admin.totalmarketing.com 1
http://patchspace.posterous.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Apple Keynote

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel

12 of 2

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
  • Heh, actually it was never me planning to write this. The one you're looking for is Tom Crayford's 'Isolation vs Rails: More Fastererer Speedy Testing Mk II Edition' talk, available here: http://shrug.org/meetings/shrug-27/

    This is well worth a look, and is the 'right way' (it's how I write code when I'm not forced to couple it to Rails) but you'd have to ask Tom where the slides are.

    Thanks for you comment, and glad you liked the presentation!
    Ash
    Are you sure you want to
    Your message goes here
    Processing…
  • Good stuff, thanks. Did you ever make the follow-up presentation?
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n
  • \n

Speedy TDD with Rails Speedy TDD with Rails Presentation Transcript

  • Speedy TDD with RailsReclaiming your feedback loop (the wrong way) Creative Commons Attribution-Noncommercial-Share Alike Ash Moran 2.0 UK: England & Wales License PatchSpace Ltd
  • ierSpeedy TDD with RailsReclaiming your feedback loop (the wrong way) Creative Commons Attribution-Noncommercial-Share Alike Ash Moran 2.0 UK: England & Wales License PatchSpace Ltd
  • What say Google about tests?
  • JavaScriptNothing unusual here
  • ClojureSeems pretty uneventful too
  • ScalaEqually unexciting
  • PythonTesting is pretty boring really
  • JavaYou’re probably ok as long as you speak German
  • C#More concerned with recruitment than code
  • FortranYou’re on your own
  • DjangoSince, in fairness, we’re testing web apps here
  • RailsYou can hear their pained cries for help
  • Why does test speed matter?
  • Red Refactor GreenThe TDD loopThis constrains how fast you can learn whetheryour code implements the specification
  • TDD orders of magnitude0.01s Full-app validation on every run0.1s Continuous development flow1s Interrupted development flow10s Check Twitter more than you check your app
  • What is the current situation?
  • Rails: one testOne RSpec exampleRSpec 2.8.0 on MRI Ruby 1.9.3
  • Rails: 16 testsBooting Rails is the constraint
  • What can we do?
  • Attack vector 1:The Ruby Runtime
  • Upgrade from Ruby 1.9.2The `require` method is much faster in 1.9.3But… Heroku currently only offers 1.9.2
  • Switch to 1.8.7 This was seriously proposed to me on the rspec-users list Baby + bathwater?
  • Attack vector 2:Pre-loading and forking Rails
  • Spinhttps://github.com/jstorimer/spinStart the server (loads Rails): spin serveRun tests spin push test/unit/product_test.rb spin push a_test.rb b_test.rb …No changes to the app whatsoeverInternally does some jiggery-pokery for RSpec
  • Guard::Spinguard spin do watch(%r{^spec/.+_spec.rb}) watch(%r{^app/(.+).rb}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^app/(.+).haml}) { |m| "spec/#{m[1]}.haml_spec.rb" } watch(%r{^lib/(.+).rb}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^app/controllers/(.+)_(controller).rb}) { |m| [ "spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/requests/#{m[1]}_spec.rb" ] }end
  • Guard::SpinFaster, but not an order of magnitude fasterOutcome: only worth it if it causes no problems
  • Sporkhttps://github.com/sporkrb/sporkRequires changing spec_helper.rbUses enough metaprogramming monkey-patching ofRails to summon a Greater Uncle Bob Daemon https://github.com/sporkrb/spork/blob/master/lib/ spork/app_framework/rails.rb#L43-80
  • Spork monkey-patchingdef preload_rails if deprecated_version && (not /^3/.match(deprecated_version)) puts "This version of spork only supports Rails 3.0. To use spork with rails 2.3.x, downgrade to spork 0.8.x." exit 1 end require application_file ::Rails.application ::Rails::Engine.class_eval do def eager_load! # turn off eager_loading, all together end end # Spork.trap_method(::AbstractController::Helpers::ClassMethods, :helper) Spork.trap_method(::ActiveModel::Observing::ClassMethods, :instantiate_observers) Spork.each_run { ActiveRecord::Base.establish_connection rescue nil } if Object.const_defined?(:ActiveRecord) …
  • Spork monkey-patching…AbstractController::Helpers::ClassMethods.module_eval do def helper(*args, &block) ([args].flatten - [:all]).each do |arg| next unless arg.is_a?(String) filename = arg + "_helper" unless ::ActiveSupport::Dependencies.search_for_file(filename) # this error message must raise in the format such that LoadError#path returns the filename raise LoadError.new("Missing helper file helpers/%s.rb" % filename) end end Spork.each_run(false) do modules_for_helpers(args).each do |mod| add_template_helper(mod) end _helpers.module_eval(&block) if block_given? end endend
  • Forking Rails? Only saves part of the time Introduces potential problems Reloading the pre-fork Hacks to make it work Can introduce many subtle bugs Doesn’t address the real problem: depending on Rails
  • Attack vector 3:Hijack code reloading forbrowser integration tests
  • Code reloadingUsed in the development environmentCalled cache_classes This is a lie! Ruby does not have real code reloading like ErlangCan be used to speed up browser integration tests Called “acceptance” here, which may not be true
  • Guard::Rails# Running with `daemon: true` because I cant figure out how to turn off enough Rails loggingguard "rails", environment: "acceptance", server: :thin, port: 3100, daemon: true do watch("Gemfile.lock") watch(%r{^(config|lib)/.*})endguard "rspec", spec_paths: %w[ spec/acceptance ], cli: "--color --format Fuubar" do watch(%r{^spec/acceptance/.+_spec.rb$})end
  • environments/acceptance.rb MyApp::Application.configure do config.cache_classes = false config.consider_all_requests_local = true config.active_support.deprecation = :log config.assets.compress = false config.action_mailer.default_url_options = { :host => localhost:3000 } config.action_mailer.delivery_method = :smtp config.action_mailer.smtp_settings = { :address => "localhost", :port => 1025, :enable_starttls_auto => false } # Disable logging for now (too much noise in Guard) # I couldnt figure out how to make it log so running this as a daemon instead # config.logger = nil # config.action_controller.logger = nil # config.action_view.logger = nil end Mongoid.configure do |config| config.logger = nil end
  • capybara-webkit testsFeedback time comparable to controller tests
  • Code reloading summarySpeeds up start time of browser tests considerablyTests must be written to work cross-processFewer issues than Spin/Spork (lesser of two evils)No help at all speeding up controller testsStill doesn’t address the real problem
  • Attack vector 4:Split the tests by dependency
  • Example: Mongoid require spec_helper require spec/environments/mongoid require_unless_rails_loaded app/models/question require_unless_rails_loaded app/models/user require spec/support/blueprints/question describe Question do describe "#populate" do let(:source_question) { Question.make(value: "Submetric 1a Q1", comment: "Submetric 1a A1") } let(:target_question) { Question.make(value: "this does not get overwritten", comment: "this gets overwritten") } before(:each) do source_question.populate(target_question) end subject { target_question } its(:value) { should be == "this does not get overwritten" } its(:comment) { should be == "Submetric 1a A1" } end end
  • spec/environments/mongoid require_relative common # Gem dependencies require mongoid if !rails_loaded? ENV["RACK_ENV"] ||= "test" # Hack to use the Mongoid.load! Mongoid.load!("config/mongoid.yml") Mongoid.configure do |config| config.logger = nil end end RSpec.configure do |config| config.before(:each) do Mongoid::IdentityMap.clear end end
  • spec/environments/common def require_unless_rails_loaded(file) require(file) unless rails_loaded? end This may be paranoia
  • spec_helper.rb def rails_loaded? Object.const_defined?(:MyApp) && MyApp.const_defined?(:Application) end
  • Guardfile (multiple guards) guard "rspec", spec_paths: %w[ spec/controllers ], cli: "--color --format Fuubar" do watch(spec/environments/rails.rb) { "spec/controllers" } watch(%r{^spec/controllers/.+_spec.rb$}) watch(%r{^app/controllers/(.+).rb$}) { |m| "spec/#{m[1]}_spec.rb" } end guard "rspec", spec_paths: %w[ spec/models ], cli: "--color --format Fuubar" do watch(spec/spec_helper.rb) { "spec/models" } watch(spec/environments/mongoid.rb) { "spec/models" } watch(%r{^spec/support/(.+).rb$}) { "spec/models" } watch(%r{^spec/models/.+_spec.rb$}) watch(%r{^app/models/(.+).rb$}) { |m| "spec/#{m[1]}_spec.rb" } watch(%r{^app/models/concerns.rb$}) { |m| "spec/models" } end
  • Running in GuardMongoid tests now running ~1s not ~10sNot brilliant, but an order of magnitude change
  • Concluding thoughts
  • How much does this help?Bypassing the Rails boot process can increasefeedback speed by an order of magnitudeWithout changing your code, you can turn a nightmareinto a bad dreamThe size of the gains depend on how manydependencies you have left
  • Is this the right way? No. Tune in next month for “Speedy TDD in Rails (the righter way”!