14. Red
Refactor Green
The TDD loop
This constrains how fast you can learn whether
your code implements the specification
15. TDD orders of magnitude
0.01s
Full-app validation on every run
0.1s
Continuous development flow
1s
Interrupted development flow
10s
Check Twitter more than you check your app
24. Spin
https://github.com/jstorimer/spin
Start the server (loads Rails): spin serve
Run tests
spin push test/unit/product_test.rb
spin push a_test.rb b_test.rb …
No changes to the app whatsoever
Internally does some jiggery-pokery for RSpec
28. Spork monkey-patching
def 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)
…
29. 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
end
end
30. 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
32. Code reloading
Used in the development environment
Called cache_classes
This is a lie! Ruby does not have real code reloading
like Erlang
Can be used to speed up browser integration tests
Called “acceptance” here, which may not be true
33. Guard::Rails
# Running with `daemon: true` because I can't figure out how to turn off enough Rails logging
guard "rails", environment: "acceptance", server: :thin, port: 3100, daemon: true do
watch("Gemfile.lock")
watch(%r{^(config|lib)/.*})
end
guard "rspec", spec_paths: %w[ spec/acceptance ], cli: "--color --format Fuubar" do
watch(%r{^spec/acceptance/.+_spec.rb$})
end
34. 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 couldn't 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
36. Code reloading summary
Speeds up start time of browser tests considerably
Tests must be written to work cross-process
Fewer issues than Spin/Spork (lesser of two evils)
No help at all speeding up controller tests
Still doesn’t address the real problem
38. 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
39. 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
40. spec/environments/common
def require_unless_rails_loaded(file)
require(file) unless rails_loaded?
end
This may be paranoia
41. spec_helper.rb
def rails_loaded?
Object.const_defined?(:MyApp) &&
MyApp.const_defined?(:Application)
end
45. How much does this help?
Bypassing the Rails boot process can increase
feedback speed by an order of magnitude
Without changing your code, you can turn a nightmare
into a bad dream
The size of the gains depend on how many
dependencies you have left
46. Is this the right way?
No.
Tune in next month for “Speedy TDD in Rails (the
righter way”!