Wednesday, December 14, 2011

(More) Specific Stubbing with RSpec

A couple of months ago we had to write code for the following feature: a company would like to reward its most valuable customers by giving them credit which they can use in their future orders.

We came up with the following solution:
class GivesCreditToPreferredCustomers
  def self.for_large_orders(sales_amount, added_credit)
    preferred_customers = Customer.has_large_purchases(sales_amount)
    preferred_customers.each do |customer|
      customer.add_credit added_credit
    end
  end
end

class Customer
  attr_reader :total_credit

  def self.has_large_purchases(sales_amount)
    puts "AR query to find buyers with large purchases"
  end

  def add_credit(amount)
    @total_credit = 0 if @total_credit.nil?
    @total_credit += amount
  end
end

describe GivesCreditToPreferredCustomers do
  specify "for large orders" do
    sales_amount = 10000
    credit_given = 100
    found_customer = Customer.new
    Customer.stub(:has_large_purchases) \
            .and_return [found_customer]

    GivesCreditToPreferredCustomers \
            .for_large_orders(sales_amount, credit_given)

    found_customer.total_credit.should == credit_given
  end
end

Take a look at the lines where the Customer's :has_large_purchases method is being stubbed: "Customer.stub(:has_large_purchases).and_return([found_customer])".
Everything is passing there, even though I have not specified any arguments. Of course: when you don't specify arguments, RSpec will take any arguments (or no arguments) and return the canned response.

A couple of months passes by and a new requirement comes in: we need to look at only the last 3 months of purchases, otherwise the company is giving away too much credit to its customers. The look back period is the same to all customers, it's safe to put it in the GivesCreditToPreferredCustomers class.

You would obviously start with modifying the spec, but your co-worker wants to get this done really quick and updates the application code like this:

class GivesCreditToPreferredCustomers
  LOOK_BACK_PERIOD = 3
  def self.for_large_orders(sales_amount, added_credit)

    # the has_large_purchases scope now takes two arguments
    preferred_customers = Customer.has_large_purchases(sales_amount, LOOK_BACK_PERIOD)
    
    preferred_customers.each do |customer|
      customer.add_credit added_credit
    end
  end
end

I execute the spec and everything passes:
.

Finished in 0.00063 seconds
1 example, 0 failures

Wow! That's quite a bit of change and nothing failed. Yet.

Let's make sure that only those messages are stubbed that have the correct arguments. I add the with() method to the stub's method chain:

describe GivesCreditToPreferredCustomers do
  specify "for large orders" do
    sales_amount = 10000
    credit_given = 100
    look_back_period = 3
    found_customer = Customer.new

    Customer.stub(:has_large_purchases) \
            # stub with arguments
            .with(sales_amount, look_back_period) \
            .and_return [found_customer]

    GivesCreditToPreferredCustomers \
            .for_large_orders(sales_amount, credit_given)

    found_customer.total_credit.should == credit_given
  end
end

Everything passes in the spec but we are now stubbing messages only where the :has_large_purchases method is called with the passed in sales amount (10,000) and the correct look back period (3).
.

Finished in 0.00062 seconds
1 example, 0 failures

Let's see what happens when the LOOK_BACK_PERIOD is changed to 2 due to a new requirement from the customer:

F

Failures:

  1) GivesCreditToPreferredCustomers for large orders
     Failure/Error: preferred_customers = Customer.has_large_purchases(sales_amount, LOOK_BACK_PERIOD)
       received :has_large_purchases with unexpected arguments
         expected: (10000, 3)
         got: (10000, 2)
     # ./describe_stub_spec.rb:5:in `for_large_orders'
     # ./describe_stub_spec.rb:38:in `block (2 levels) in '

Finished in 0.00104 seconds
1 example, 1 failure

This would happily pass with a stub where I don't specify the arguments but it fails here where the stub argument is strictly defined.

Adding the argument is a little bit more work but the benefits are huge: you are exercising not only the message sent to the object but the arguments that the message is sent with.

Happy stubbing!

You can review the example I created for this blog post in this Gist.

Tuesday, October 11, 2011

Running Fast RSpec Tests With and Without Rails

So you got out of the controller and from Active Record and you're ready to test your services without Rails?

I'll describe how you can trust your fast Rails specs by defining classes safely and ways you can execute them with or without Rails. All of my examples are a continuation of my previous blog post, I recommend reading that first before you proceed with this one.

The FindsUsers service is very simple:
# lib/service/finds_users.rb
module Service
  class FindsUsers
    def self.all
      User.active.map { |user| ::DTO::User.new(user) }
    end
  end
end
And this is how I created the first spec without Rails:
# spec/units/service/finds_users_spec.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", ".."))
$: << File.join(APP_ROOT, "lib")
$: << File.join(APP_ROOT, "spec/units")

module ActiveRecord
  class Base; end
end
class User < ActiveRecord::Base; end

require 'ostruct'
require 'service/finds_users'
require 'factory/for_user'
require 'dto/user'

describe Service::FindsUsers do
  let(:users) { Factory::ForUser.make_two }

  describe "converts the found users to DTO::User" do
    before { User.stub(:active).and_return users }
    subject { Service::FindsUsers.all }

    its(:size) { should == 2 }
    its(:first) { should be_instance_of ::DTO::User }
    its(:last) { should be_instance_of ::DTO::User }
  end
end
Please take a look at line 9, where I declared the User class. I need to do this since I don't reference the application's Active Record models in these specs. I don't need to, all I care is that it's some kind of User class that has an :active class method on it.

I also declared a test dummy for ActiveRecord::Base. It doesn't matter what it does, I just want to make sure my User class declaration is as close to the original Active Record model as possible.

When I run the specs they all pass:
...
Finished in 0.00223 seconds
3 examples, 0 failures
rspec spec/units/service/finds_users_spec.rb 0.29s user 0.09s system 96% cpu 0.392 total

It works great, but there are a few lines that will be used in other specs. I move those into the spec/units/spec_helper.rb file.
# spec/units/spec_helper.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
$: << File.join(APP_ROOT, "lib")
$: << File.join(APP_ROOT, "spec/units")
$: << File.join(APP_ROOT, "spec/units/factory")

require 'ostruct'

# Defining an ActiveRecord::Base dummy for models
module ActiveRecord
  class Base; end
end
Now my finds_users_spec.rb file is shorter and cleaner:
# spec/units/service/finds_users_spec.rb
require 'units/spec_helper'

# ActiveRecord::Base is defined in spec/units/spec_helper.rb
class User < ActiveRecord::Base; end

require 'service/finds_users'
require 'factory/for_user'
require 'dto/user'

describe Service::FindsUsers do
  let(:users) { Factory::ForUser.make_two }

  describe "converts the found users to DTO::User" do
    before { User.stub(:active).and_return users }
    subject { Service::FindsUsers.all }

    its(:size) { should == 2 }
    its(:first) { should be_instance_of ::DTO::User }
    its(:last) { should be_instance_of ::DTO::User }
  end
end
Testing the FindsDiscussion service is just as simple:
# spec/units/service/finds_discussion_spec.rb
require 'units/spec_helper'

# ActiveRecord::Base is defined in spec/units/spec_helper.rb
class Discussion < ActiveRecord::Base; end

require 'service/finds_discussion'
require 'factory/for_discussion'
require 'dto/discussion'
require 'dto/comment'

describe Service::FindsDiscussion do
  let(:discussion) { Factory::ForDiscussion.make_one }

  describe "looks up a discussion and converts it to DTO" do
    before { Discussion.stub(:find).and_return discussion }
    subject { Service::FindsDiscussion.for 24 }

    it { should be_instance_of ::DTO::Discussion }
  end
end
I also need to declare the Discussion class here, so I can stub it out for my service.

They all pass when I execute the entire spec/units suite:

....
Finished in 0.00513 seconds
4 examples, 0 failures
rspec spec/units 0.28s user 0.09s system 96% cpu 0.387 total

BUT WAIT!!

My User Active Record model has the scope :active that I verify it by loading up Rails in this spec:
# spec/models/user_spec.rb

# This spec is using the spec/spec_helper.rb file that loads up Rails with Active Record!
require 'spec_helper'

describe User do
  it { should respond_to :active }
end
I run its slow AR spec and a unit spec with this command in the terminal:
$: time rspec spec/models/user_spec.rb spec/units/service/finds_users_spec.rb
It takes a little while - 4 seconds - but everything passes.

....
Finished in 0.04386 seconds
4 examples, 0 failures
rspec spec/models/user_spec.rb spec/units/service/finds_users_spec.rb 3.49s user 0.59s system 100% cpu 4.083 total

But when I change the files around - executing the spec that does not need Rails first and the model spec that uses Rails second:
$: time rspec spec/units/service/finds_users_spec.rb spec/models/user_spec.rb
The specs are executed fast, but the AR model spec failed:

...F
Failures:

  1) User
    Failure/Error: it { should respond_to :active }
      expected #<User:0x00000100a53538> to respond to :active

    # ./spec/models/user_spec.rb:5:in `block (2 levels) in <top (required)>'

Finished in 0.00248 seconds
4 examples, 1 failure

You might be puzzled why this spec failed, but the explanation is rather simple: in the first case we ran the AR spec first. It loaded up and used the AR User model, the spec passed. Then we opened the User class in our fast spec, stubbed out a method on the User Active Record model and the service spec passed as well.

In the second case we defined our User class for our fast spec, executed the spec and they all passed. Then the AR model spec picked up the already declared User class - which was not the AR User model - and since it did not have the :active scope defined, it failed.

This is exactly what happened when we started executing all our specs - both non-Rails and Rails specs together - on our build server. The spec execution order was different on CentOS and different on our local OS X development environment. Everything passed locally, but had quite a few errors on the build server. We obviously had to find a solution.

First of all, redefining classes all over the specs just wasn't a good idea. I moved all my redefined classes into spec/units/spec_helper.rb from the different specs.
# spec/units/spec_helper.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
$: << File.join(APP_ROOT, "lib")
$: << File.join(APP_ROOT, "spec/units")
$: << File.join(APP_ROOT, "spec/units/factory")

require 'ostruct'

# Defining an ActiveRecord::Base dummy for models
module ActiveRecord
  class Base; end
end

# I moved the redefined classes here
class User < ActiveRecord::Base; end
class Discussion < ActiveRecord::Base; end
Look at line 15 and 16 in the spec_helper, the redefined classes are now in one single place as opposed to having them scattered all over the specs.
I ran the fast specs without Rails again and they were all passing.

All I had to do to get the specs passing regardless of file order was including the Rails-aware spec_helper into the spec/units/spec_helper.rb file that loaded up Rails with the real Active Record models (line 2 below):
# Including the full stack spec_helper, loads the AR models with Rails
require 'spec_helper'

# spec/units/spec_helper.rb
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", ".."))
$: << File.join(APP_ROOT, "lib")
$: << File.join(APP_ROOT, "spec/units")
$: << File.join(APP_ROOT, "spec/units/factory")

require 'ostruct'

# Defining an ActiveRecord::Base dummy for models
module ActiveRecord
  class Base; end
end

# I moved the redefined classes here
class User < ActiveRecord::Base; end
class Discussion < ActiveRecord::Base; end
Now when I execute the specs starting with the fast spec first it loads up Rails and in about 4 seconds I know that all the specs are passing regardless of what file order was used at execution time.

This change alters the User and Discussion class declarations as well. They are not redefined classes any more, they are open classes in the execution context. I am not modifying their behavior, I am just opening up the classes and leaving them unchanged.

A script in the build process can change the spec/units/spec_helper.rb file to include the full stack spec_helper.rb file.

This might seem like a lot of voodoo for some, but I am working on a fairly large Rails app and it takes about 23 seconds to execute one spec with Rails. I believe with just a little bit of meta programming trick you can enjoy very fast feedback loop making you more effective at writing software.

Wednesday, September 7, 2011

Get out of my Controller! And from Active Record, too!

I wrote about Running Rails Rspec Tests Without Rails a couple of months ago. The examples I used were very high level and focused on stubbing out Rails in my tests in order to achieve rapid feedback.

A couple of months have passed and the topic is getting more and more buzz thanks to Corey Haines and Robert "Uncle Bob" Martin.

I've been getting many questions on how I abstract away from Rails' Active Record, how do I use service objects to lighten up my controllers. I'll try to describe all that in this blog post.

Imagine an application where you have topics that people can comment on. The Active Record models are something like this:
class User < ActiveRecord::Base
  # These fields are defined dynamically by ActiveRecord
  attr_accessor :id, :full_name
end

class Discussion < ActiveRecord::Base
  # These fields are defined dynamically by ActiveRecord
  attr_accessor :id, :title, :body, :comments
end

class Comment < ActiveRecord::Base
  # These fields are defined dynamically by ActiveRecord
  attr_accessor :id, :text, :entered_by
end
And here is their relationships:

This is pretty simple: Discussion has many comments and a comment was entered by a user. Fantastic!
But what do you do when your customer comes to you and asks you to get not only the comments for a given discussion but she would like to see each user with their comments made on the specific discussion.

Here is the page layout:



The Active Record models will perfectly match the view hierarchy on the left. But you are looking at the same data from a different angle on the right hand side.
How are you going to get that data into the view?

Here are some of your options:
  1. Create a view helper that grabs the user's comments from the DB
  2. Add a new field to the User AR model to hold the comments
  3. Use Plain Old Ruby Object (PORO) models on top of AR models and use service objects
Number one is beyond bad. You are actually iterating through the users and hitting the database for every single user to get their comments. BAD! Never do that! It's a very expensive operation: connection is opened, query is executed, AR models are built up from the result set. You already have the data in memory. Use it!

Number two is better but I don't like that either. By adding a field to the User AR model you can do all the data processing in the controller and present that data to the view. This way the view iterates over the users and for each user it iterates over its comments. There is no lookup from the view but you are polluting the AR model with a field that is specific to one particular view. The User AR model is a core object in your application, you want to keep it very clean. Other developers should not be puzzled by an attr_accessor called :comments.

Here is what I'd do: create small model objects that wrap the AR models. Use service objects to populate these POROs and prepare them exactly as the view needs it. Then the view is very simple: it iterates over these model objects and uses their properties.
I call these PORO objects DTOs or Data Transfer Objects. They serve custom data from the model to the view.

Here is how a UserDTO looks:
module DTO
  class User
    attr_reader :source_object, :id, :full_name
    attr_accessor :comments
    def initialize(source_object)
      @source_object = source_object
      @id = source_object.id
      @full_name = source_object.full_name
    end
  end
end
I keep a reference to the original AR model through the @source_object variable. Whatever field I can populate from the source object I do that in the object's initializer. But in our case there is an extra field that does not exist in the source model: comments. This field is declared but not yet populated. The service object will take care of that.

The controller's index action has to do three things:
  • Get the currently viewed discussion from the database
  • Retrieve all the users
  • Find the users' comments under the current discussion
You could place all the code into the controller's action, but you'll have a bloated controller thats very hard to test and the logic will be impossible to reuse.
I use very granular service objects from the controller.
# Services used in the app
module Service
  class FindsDiscussion
    def self.for(id)
      # This is very high level
      ::DTO::Discussion.new(Discussion.find(id))
    end
  end

  class FindsUsers
    def self.all
      User.all.map { |user| ::DTO::User.new(user) }
    end
  end

  class SetsComments
    def self.on_users(users, comments)
      # There is no trip to the DB!
      users.each do |user|
        user.comments = comments.select do |comment|
          user.source_object.id == comment.source_object.entered_by
        end
      end
    end
  end
end
Look at how small they are! The first and second service looks up data in the database, but the third one is using an in memory lookup. This is how I am saving the trip to the data store.
SRP is strictly followed, these little logic classes are super easy to test and using them from the controller is straightforward:
class DiscussionsController < ApplicationController
  attr_reader :users, :discussion

  def index
    @users = Service::FindsUsers.all
    @discussion = Service::FindsDiscussion.for(params[:id])
    Service::SetsComments.on_users(@users, @discussion.comments)
  end
end
You are creating many more small classes, but that's OK. They are easy to understand, easy to test and you can use them like little LEGO blocks to construct the logic your controller needs.

You can find the examples I used in the blog post in this Gist.

Monday, June 6, 2011

Spec it or not?

I had a conversation with my friend Joe Fiorini a couple of days ago about this particular code:
module ApplicationHelper
  def gravatar_for(email)
    image_tag Utils::Gravatar.for(email), size: "30x30"
  end
end
He showed me this code and my first question was: "Did you write tests for it?" He said: "Why? This is a simple method. What should I test?"
We both agreed that there isn't much that could go wrong there.

But:
  1. Writing a spec against this code is quick and easy.
  2. I'd much rather look at a spec documentation that describes this code than the code itself.
  3. A spec describes intent.
It took me about 4 minutes to write this spec:
require 'units/spec_helper'
require 'application_helper'

describe ApplicationHelper do
  context "#gravatar_for(email)" do
    specify "provides an image tag with Gravatar url" do
      dummy = Object.new.extend ApplicationHelper # It's a module

      Utils::Gravatar.stub(:for).and_return("some url")
      dummy.stub(:image_tag).and_return("some image tag")

      dummy.gravatar_for("some_email").should == "some image tag"
    end
  end
end
I am not using mocks, I like to keep my tests "loose". I want to make sure when I call this helper I get the result I expect through the canned responses provided by the stubs.

The image_tag helper needs a url. This url is provided by a utility class method, stubbed out on line 9.

It's important to mention that I am not testing Rails' image_tag helper. I leave that to the Rails core developers and contributors. I want to make sure that the method image_tag is recognized in the given context and it returns the string I expect.

Once I execute the spec, this is the output:
ApplicationHelper
  #gravatar_for(email)
    provides an image tag with Gravatar url

Finished in 0.00151 seconds
1 example, 0 failures
What did I mean by "communicating intent"?
Let's say a new developer comes to the team and decides to change the gravatar_for method like this:
def gravatar_for(email)
  #image_tag Utils::Gravatar.for(email), size: "31x30"
  url_for some_kind_of_named_route(email)
end
As soon as he runs the spec the error is obvious:
F
Failures:

  1) ApplicationHelper#gravatar_for(email) provides an image tag with Gravatar url
     Failure/Error: dummy.gravatar_for("some_email").should == "some image tag"
     NoMethodError:
       undefined method `some_kind_of_named_route' for #<Object:0x01008e8838>
     # ./app/helpers/application_helper.rb:7:in `gravatar_for'
     # ./spec/units/helpers/application_helper_spec.rb:12:in `block (3 levels) in <top (required)>'

Finished in 0.00139 seconds
1 example, 1 failure
To me this is a good warning sign that suggests the following: "You can change the method behavior, but the original developer meant it the way it's described in the spec. Now please fix the spec so it's green again. Oh, and make sure you run the full stack automated acceptance tests before you push your code."

Knowing metaprogramming, stubbing and mocking makes it easy to write specs.
I would have a totally different opinion if it took a lot more code and ceremony to do it.

Friday, April 15, 2011

Running Rails Rspec Tests - Without Rails

I opened up my twitter client this afternoon and I saw "54 Messages, 28 Mentions". I tell you honestly, the first thought I had was: my twitter account had been hacked. Then I started to comb through the messages and I found out what happened. It all started with a tweet from Joe Fiorini.


We both worked together on a large Rails application. The application was a little light on tests, so I asked the other developers why they are not writing more specs? The answer was all too familiar: "it just takes forever to run them". Yup, Rails had to load up, schema needed to be verified, the entire universe had to be included and 30 seconds later our specs were executed.

We started creating POROs - Plain Old Ruby Objects - as pure services and put their RSpec tests into APP_ROOT/spec/units directory. Our goal was to keep the execution time under or around 2 seconds. Sure, it's easy when you don't have to load Rails controllers or active record models. But what happens when you have to?
This post will explain that.

The controller I used for this example is simple:
class TracksController < ApplicationController
  def index
    signed_in_user
  end

  def new
    @track = Track.new
  end

  def create
    feed = params[:track]["feed"]
    @track = TrackParserService.parse(feed)

    unless @track.valid?
      render :action => 'new'
      return
    end

    @track.save_with_user!(signed_in_user)

    render :action => 'index'
  end

  def destroy
    Track.find(params[:id]).destroy

    @user = User.first
    render :action => 'index'
  end

  private

  def signed_in_user
    # No authentication yet
    @user ||= User.first
  end
end
The first controller action I wanted to test was "index".

I created the directory structure APP_ROOT/spec/units/controllers and saved my file in this directory under the name tracks_controller_spec.rb.

I started out with this code:
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", ".."))
$: << File.join(APP_ROOT, "app/controllers")

require 'tracks_controller'

describe TracksController do
  
end
You could move the first two lines into a spec_helper, I wanted to keep it here for clarity.

I received the following error:
`const_missing': uninitialized constant Object::ApplicationController (NameError)

No worries: TracksController inherits from ApplicationController, it's part of my app, I just had to require it.
require 'application_controller'
And the error:
`const_missing': uninitialized constant Object::ActionController (NameError)

This was the point where I had to require Rails.

Instead of doing that, I just defined the class myself so the controller was aware of it. I also needed to declare the class method "protect_from_forgery", but I left the implementation blank. Please note that the class declaration is above the require statements.
Here is the entire spec after my changes:
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", ".."))
$: << File.join(APP_ROOT, "app/controllers")

# A test double for ActionController::Base
module ActionController
  class Base
    def self.protect_from_forgery; end
  end
end

require 'application_controller'
require 'tracks_controller'

describe TracksController do
  
end
Running the spec:

Finished in 0.00003 seconds
0 examples, 0 failures

The first test just ensures that the User active record model will load the first user if the @user instance is nil.
describe TracksController do
  let(:controller) { TracksController.new }

  specify "index action returns the signed_in_user" do
    # setup
    user = stub
    User.stub(:first).and_return user

    # execute action under test
    returned_user = controller.index

    # verify
    returned_user.should == user
    controller.instance_variable_get(:@user).should == user
  end
end
The test is straightforward. User model is returning a stub - I don't really care what that returned object is, I just check if they're the same object. In the verification part I made sure that the instance variable was set properly. Great that your can check an un-exposed field on a object with a little bit of metaprogramming?

I executed the spec and received the following error:

Failures:

  1) TracksController index action returns the signed_in_user
    Failure/Error: User.stub(:first).and_return user     NameError:       uninitialized constant RSpec::Core::ExampleGroup::Nested_1::User

Well, I need to require the User model to fix this error. Or do I? I am not using any functionality of the User class - whatever I am using is stubbed out. I just defined the class without any implementation.

This line was added to the spec right above the describe block.
class User; end
I execute the test and it's all green.

TracksController
  index action returns the signed in user

Finished in 0.00079 seconds 1 example, 0 failures 1.26s user 0.28s system 99% cpu 1.546 total

1.5 seconds is not all that bad to run a controller action test.

Let me describe how I tested the "create" action.
Take a look at the controller code above and review what it does. The @track instance is constructed by the TrackParserService class' parse method. Then active record validates it and if the model is invalid the controller's "new" action is rendered.

Here is the spec for that:
context "when the model is not valid" do
  it "renders action => 'new'" do
    # define a method for params - TracksController is unaware of it
    controller.class.send(:define_method, :params) do
      {:track => "feed"}
    end

    track = stub(:valid? => false)
    TrackParserService.stub(:parse).and_return(track)

    render_hash = {}
    # hang on to the input hash the render method is invoked with
    # I'll use it to very that the render argument is correct
    controller.class.send(:define_method, :render) do |hash_argument|
      render_hash = hash_argument
    end

    controller.create

    # verify the render was called with the right hash
    render_hash.should == { :action => 'new' }
  end
end
I used Ruby's metaprogramming again to set up the params hash. It really doesn't matter what's in it, since I stub out the TrackParserService. The method "render" comes from Rails, I had to define that as well. Please note that I record what the render method was invoked with, this way I can verify that the input hash was correct.
I also had to define - with no implementation - the Track and TrackParserService classes.

When I executed the specs, all of them passed:

TracksController
  index action returns the signed in user
  new action returns an instance of Track
  when the model is not valid
    renders action => 'new'

Finished in 0.00203 seconds
3 examples, 0 failures
bundle exec rspec spec/units/controllers/tracks_controller_spec.rb -fd 1.32s user 0.29s system 99% cpu 1.614 total

You can review the entire example in this gist.

This code is rough. I just used it to show you how we try to keep our test execution fast. I acknowledge that I am doing some very dangerous stubbing here. However, I have the higher level cucumber tests to protect me against unexpected errors.

I can't tell you what it means to run all of my 150+ specs within 2 seconds. I think it's a little bit of an extra work, but it's well worth the effort!

Sunday, April 10, 2011

Rapid Feedback

I am not a WPF expert. In fact, I don't know it well enough. But let me tell you what it was like working on a WPF project last year.


It was 10 o'clock in the morning and I sat in my cubicle. I had to pull the latest changes from the source control server. I had to run a couple of batch files to compile all the code that took about 3 minutes. In the mean time I fired up Visual Studio 2010 and ran my latest unit tests to make sure everything was in good shape.

I started up the WCF services, 86 of them, and a little later I was ready to run the UI app. It took another 30 to 40 seconds to load and get to the login screen. I logged in and selected from the menu where I wanted to get to. That page was a list of items, I had to select one of them just to get to the detail page. Finally, I was there!

Let me sum it up:
* 180 seconds to compile the app
*   50 seconds to fire up all WPF services
*   20 seconds to start the WPF UI App
*   60 seconds to log in and go to the page I had to modify
TOTAL: 310 seconds

My task was adding a new TextBox to this page. Simple. I opened up the XAML file which was an ugly xml file with weird namespaces and special attributes all over. I grabbed a TextBox XAML code from somewhere, pasted it in, made sure all the namespaces were fine and I was ready to run it.

I had to shut down the UI app, compile the UI project, start it up again, log in, select the menu option to get to the list page and choose one item to see the detail.

Here is how long this took:
* 30 seconds to compile the UI app
* 20 seconds to start the WPF UI App
* 60 seconds to log in and go to the page I had to modify
TOTAL: 110 seconds

And it turned out that I did not set up the Grid for this TextBox properly, so I had to do some minor tweaks to the XAML page. I did that, killed the UI app, complied the code, ran the WPF UI app, logged in, went to the page and 110 - or one hundred and ten - seconds later I verified that all look good.

But this was the fast part. Once I had all the UI set up properly, I had to get under the hood and modify the domain object. The change was "simple": just add a text field to the database, modify the domain object, set up the NHIbernate mapping, change the Data Transfer Object, add this field to it and set up its mapping if I had to.
Now to make sure all this worked I had to shut down the UI app, the WPF services. Compile the code, regenerate the NHibernate mappings, fire up the WPF services, run the UI, log in, select the page and pick an item to get to its detail. Simple, right?

Here is the break down:
* 30 seconds to compile the Data Access Code
* 30 seconds to regenerate the NHibernate mapping xml
* 50 seconds to fire up all WCF services
* 20 seconds to start the WPF UI App
* 60 seconds to log in and make sure that all looks good
TOTAL: 190 seconds

Wait! 3 minutes just to see if everything is working properly?

Give me a break.

What company with a tight budget and ever approaching deadlines could afford spending 3 minutes just to see if a simple change is functioning properly or not? Who would dare to touch the existing code to clean it up a bit?

One of the great things I like about working with Ruby and Rails is the rapid feedback. No, I am not talking about how long it takes to execute my - Rails disconnected RSpec - tests. (I'll try to write about that in an upcoming post.) I just change the code, hit the browser's refresh button and about 5 seconds later I have the page loaded, the session preserved and I have the answer.

I am talking about 5 seconds and not a couple of minutes.

Wednesday, February 16, 2011

First Month Without Windows

It's been a little over than a month since I left the Microsoft Universe behind. I had used OS X and Linux before - mostly in the evenings and weekends - but I made the jump finally and I only touch Windows when I browse the web on my wife's laptop. And I just couldn't be happier...

Life on the Mac

I have a pretty powerful Mac. It has an Intel Core i5 CPU and I upgraded the RAM from 4GB to 8GB as soon as I received it. I don't have an SSD just yet, but I am still pretty happy with its performance. Whenever I feel like checking the current state of the machine I just run "htop" in the terminal.

I started out using Firefox for my work email, but I write automated tests in it as well and I had to find another solution. "Before I ran out of browsers" I investigated my options. I could have purchased Mailplane but I did not feel I needed a full blown app for it. I found Fluid, a site specific browser. Now I can open my work email just like any other application and I am still using a browser inside. I found a nice PNG file that I set up with it and the app looks just like a native app when I tab between applications. Here is how it appears under the Applications folder:

To organize my thoughts and notes I started using Evernote. It's a great tool for note taking but I think it does an even better job at organizing them. I even installed the Evernote Chrome extension:

I don't use the mouse - or track pad - to launch an application. First I used Spotlight but switched to Alfred App recently. It's fast and I can use keyboard shortcuts. This image tells it all:

Living my life in the Terminal (iTerm)

After using the terminal for a couple of weeks I switched to iTerm2. I don't use all its neat features just yet but I do like the split view mode. I have cucumber features running on one side and rails logs on the other.

I am still learning (who doesn't) and tweaking all the different configuration options of my ~/.vimrc and ~/.zshrc files. I am particularly happy with this addition to my ~/.vimrc file that ignores my arrow keys when I am in command mode. No more arrows to move around!

" Ignore arrow keys in vim
:map <Left> <Nop>
:map <Right> <Nop>
:map <Up> <Nop>
:map <Down> <Nop>
:map <PageUp> <Nop>
:map <PageDown> <Nop>
:map <Home> <Nop>
:map <End> <Nop>

:map! <Left> <Nop>
:map! <Right> <Nop>
:map! <Up> <Nop>
:map! <Down> <Nop>
:map! <PageUp> <Nop>
:map! <PageDown> <Nop>
:map! <Home> <Nop>
:map! <End> <Nop>

I even started listening to Pandora in the terminal through Pianobar.

I have used Git before, but my skills are not where it should be. I started reading the book Pragmatic Version Control Using Git which I'd recommend to anybody who wants to go deep with Git. You should also check out the great Git Immersion class created by EdgeCase.

My Typing Sucks

Well, maybe it's not that bad, but my typing could and should improve. It's just not fast enough and I don't use all my fingers. I need to take my eyes off the monitor and look at the keyboard when I try to use special key commands that I have not used much before. And my typing is not accurate, I frequently have to go back and fix words that I mistyped.
Unacceptable. I wonder if companies should check in an interview how well a candidate can type.

I used the web site typingweb.com and an app called aTypeTrainer4Mac to practice. I am still not where I'd like to be, but I am working on it.

Thanks to Joe Fiorini for showing me endless tips mentioned in this blog post.