Pivotal Labs

Main menu

Skip to primary content
Skip to secondary content
  • About
  • Case Studies
  • Team
    • Executives
    • Locations
      • San Francisco (HQ)
      • Boston
      • Boulder
      • Denver
      • London
      • Los Angeles
      • New York
  • Community
    • Blogs
    • Tech Talks
    • Events
  • Careers
    • Lifestyle
    • Principles & Practices
    • Benefits
    • FAQ
    • Apply
  • Contact
    • Press Room
    • Press Releases
    • In The News
    • Press Kit
  • All
  • Labs
  • Standup
  • Tracker

Monthly Archives: May 2008

Pivotal Labs

Standup 05/30/2008

Pivotal Labs
Friday, May 30, 2008

Interesting Things

  • ActiveRecord’s #method_missing takes precedence over private methods, which means you cannot simply mark “private” database-derived attributes.
    code:

    # File: app/models/rock_star.rb
    #
    # == Schema Information
    # Schema version: 1
    #
    # Table name: rock_stars
    #
    #  id            :integer         not null, primary key
    #  real_name     :string(255)
    #  band_name     :string(255)
    #  personal_life :string(255)
    #
    
    
    class RockStar < ActiveRecord::Base
      def method_missing(method, *arguments, &block)
        puts "I see you've sent my #{method} back and my ActiveRecords and they're all scratched"
        super
      end
    
    
      private
    
    
      def personal_life=(arg)
        puts "Vanish in the air you'll never find me"
        attributes[:personal_life] = arg
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> sting = RockStar.new(:real_name => 'Gordon Sumner', :band_name => 'The Police')
    I see you've sent my real_name= back and my ActiveRecords and they're all scratched
    => #<RockStar id: nil, real_name: "Gordon Sumner", band_name: "The Police", personal_life: nil>
    >> sting.personal_life = "I'll be watching you"
    I see you've sent my personal_life= back and my ActiveRecords and they're all scratched
    => "I'll be watching you"
    

    Potential solutions:

    • Convention… name “private” database attributes with leading underscores
    • Exception:

      class RockStar < ActiveRecord::Base
        def personal_life=(arg)
          raise "Protest is futile, nothing seems to get through"
        end
      end
      
    • Have another? Post a comment.

  • ||= (”or equal”) blows up you have a public “writer”, but a private “reader”; makes sense, but still worth a mention.
    code:

    class Model < ActiveRecord::Base
      def field_name=(arg)
        @field_name = arg
      end
    
    
      private
    
    
      def field_name
        @field_name
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> instance = Model.new
    => #<Model id: nil, field_name: nil>
    >> instance.field_name = 'lala'
    => "lala"
    >> instance.field_name ||= 'dodo'
    NoMethodError: private method `field_name' called for #<Model:0x17df6d0>
      from /Library/Ruby/Gems/1.8/gems/activerecord-2.0.2/lib/active_record/attribute_methods.rb:205:in `method_missing'
      from (irb):4
    
  • ActiveRecord writers always return the passed in argument, even if you define some other return value. This also makes sense — necessary for chaining, etc., but what the heck…
    code:

    class Model < ActiveRecord::Base
      def field_name=(arg)
        @field_name = arg
        return "custom return value"
      end
    end
    

    script/console:

    Loading development environment (Rails 2.0.2)
    >> instance = Model.new
    => #<Model id: nil, field_name: nil>
    >> instance.field_name = 'lala'
    => "lala"
    
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Dan Podsedly

Tracker public beta

Dan Podsedly
Friday, May 30, 2008

We’ve decided to share Tracker with the agile community. Details of the launch have yet to be worked out, including an exact date, but please register here if you’re interested in participating in the beta.

Stay tuned for more details!

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Better Javascript testing through ScrewUnit

Pivotal Labs
Monday, May 12, 2008

The following is an excerpt of ScrewUnit’s new copious documentation:

Writing Good Tests

A great test maximizes these features:

  • it provides documentation, explaining the intended functioning of the system as well as how the source code works;
  • it supports ongoing development, as you bit-by-bit write a failing test and make it pass;
  • it supports later refactorings and prevents regression as you add other features;
  • and it requires little modification as the implementation of the system changes, especially changes to unrelated code.

This section focuses principally on tests as documentation. To provide documentation, as well as support future modification, a test should be readable and well organized. Here are some recommendations on how to do just that.

Use Nested Describes to Express Context

Often, when you test a system (a function, an object), it behaves differently in different contexts. Use nested describes liberally to express the context under which you make an assertion.

describe("Caller#prioritize", function() {
  describe("when there are two callers in the queue", function() {
    describe("and one caller has been waiting longer than another", function() {
      ...
    });
  });
});

In addition to using nested describes to express context, use them to organize tests by the structural properties of your source code and programming language. In Javascript this is typically prototype and function. A parent describe for a prototype contains nested describes for each of its methods. If you have cross-cutting concerns (e.g., related behavior that spans across methods or prototypes), use a describe to group them conceptually.

describe("Car", function() {
  describe("#start", function() {
  });

  describe("#stop", function() {
  });

  describe("callbacks", function() {
    describe("after_purchase", function() {
    });
  });

  describe("logging", function() {
  });
});

In this example, one parent describe is used for all Car behavior. There is a describe for each method. Finally, cross-cutting concerns like callbacks and logging are grouped because of their conceptual affinity.

Test Size

Individual tests should be short and sweet. It is sometimes recommended to make only one assertion per test:

it("chooses the caller who has been waiting the longest", function() {
  expect(Caller.prioritize()).to(equal, caller_waiting_the_longest);
});

According to some, the ideal test is one line of code. In practice, it may be excessive to divide your tests to be this small. At ten lines of code (or more), a test is difficult to read quickly. Be pragmatic, bearing in mind the aims of testing.

Although one assertion per test is a good rule of thumb, feel free to violate the rule if equal clarity and better terseness is achievable:

it("returns the string representation of the boolean", function() {
  expect($.print(true)).to(equal, 'true');
  expect($.print(false)).to(equal, 'false');
});

Two tests would be overkill in this example.

Variable Naming

Name variables descriptively, especially ones that will become expected values in assertions. caller_waiting_the_longest is better than c1.

Dividing code between tests and befores

If there is only one line of setup and it is used in only one test, it may be better to include the setup in the test itself:

it("decrements the man's luck by 5", function() {
  var man = new Man({luck: 5});

  cat.cross_path(man);
  expect(man.luck()).to(equal, 0);
});

But in general, it’s nice to keep setup code in before blocks, especially if the setup can be shared across tests.

describe('Man', function() {
  var man;
  before(function() {
    man = new Man({luck: 5});
  });

  describe('#decrement_luck', function() {
    it("decrements the luck field by the given amount", function() {
      man.decrement_luck(3);
      expect(man.luck()).to(equal, 2)
    });
  });
  ...

});

Preconditions

It is ideal, if there is any chance that your preconditions are non-obvious, to make precondition asserts in your test. The last example, were it more complicated, might be better written:

it("decrements the luck field by the given amount", function() {
  expect(man.luck()).to(equal, 5);

  man.decrement_luck(3);
  expect(man.luck()).to(equal, 2)
});

Whitespace, as seen here, can be helpful in distinguishing setup and preconditions from the system under test (SUT) and its assertions. It is nice to be consistent in your use of whitespace (e.g., “always follow a group of preconditions by a newline”). But it is better to use whitespace as makes the most sense given the context. As with everything in life, do it consciously and deliberately, but change your mind frequently.

Behavioral Testing

Behavioral testing, that is, asserting that certain functions are called rather than certain values returned, is best done with closures. The dynamic nature of JavaScript makes mocking frameworks mostly unnecessary.

it("invokes #decrement_luck", function() {
  var decrement_luck_was_called = false;
  man.decrement_luck = function(amount) {
    decrement_luck_was_called = true;
  });

  cat.cross_path(man);
  expect(decrement_luck_was_called).to(equal, true);
});

Extensive Documentation for ScrewUnit is now available. Download it here:

http://github.com/nkallen/screw-unit/tree/master

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 5/9/2008

Pivotal Labs
Friday, May 9, 2008

Interesting

  • attr_readonly marks an attribute as, ah, read only — use it to tell ActiveRecord that an attribute should not be a part of update operations. Rails uses attr_readonly internally with counter caches (search for “counter_cache” under ActiveRecord::Associations::ClassMethods) since counter caches are incremented/decremented directly in the database with sql. Without attr_readonly, subsequent updates of the counter_cache’d model would revert the counter to the value of the counter at the time the model was loaded.

Note: attr_readonly was either buggy or not exposed prior to 1.2.3. If you are using a version of rails prior to 1.2.3 you can do this instead:

def attributes_with_quotes(include_primary_key = true)
  attributes.inject({}) do |quoted, (name, value)|
    if column = column_for_attribute(name)
      # original:
      # quoted[name] = quote_value(value, column) unless !include_primary_key && column.primary
      quoted[name] = quote_value(value, column) unless !include_primary_key &&
          (column.primary || ["your_attributes", "listed_here"].include?(column.name))
    end
    quoted
  end
end

Ask for Help

  • help: Looking for recommendations on converting an existing schema to a new schema. We are considering dumping the existing schema to yaml (using ar_fixtures) and making the transformations there.
    • answer #1: One recent project had a “liberate” script that extracted information from the legacy database via sql statements and constructed AR model objects as necessary. The liberate script grew to some 1500 lines of code and was refactored many times.
    • answer #2: Another project did the data migration by first importing the legacy database and then using rails migrations as needed to transform the data to the new schema. Most of the migrations used sql for the transformations. These migrations did not have associated unit tests.
    • Please offer your suggestions in the comments.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 5/8/2008

Pivotal Labs
Thursday, May 8, 2008

Interesting

  • Firebug 1.2 Alpha works with Firefox 3 Beta nightly builds
  • Firefox 2 and Firefox 3 can peacefully coexist on the same system with different profiles

Ask for Help

  • help: “JsUnit doesn’t work with Firefox 3″
    • answer #1: Apparently not with the file based url, but does work with the http url
    • answer #2: “There appears to be what some consider a bug in Firefox 3 in which JS access across frames has changed from Firefox 2…” (Follow that link for an interim fix starting at “Here’s the good news:”)
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

The Law of Demeter is a Piffle

Pivotal Labs
Wednesday, May 7, 2008

One of The Blabs’ most controversial articles was Lovely Demeter, Meter Maid, in which Pivotal and Thoughtworks battle over which Agile consultancy has the better understanding of the Law of Demeter, and which has better hair and music taste (seriously).

I have never found this “law” very persuasive.

  • The bizarre, culturally loaded analogy about a paperboy and a wallet says nothing insightful about encapsulation boundaries as they arise in real software systems.
  • The blogosphere’s endless scholastic hermeneutics of the law’s 4 allowances for message sending is a masturbatory philosophical enterprise with nothing relevant to real-world software.
  • The Mockist’s insistence on easy mockability is of dubious merit–build better mocking frameworks!
  • And the few practical, real merits that arise in from following the Law of Demeter are better arrived at using other techniques, such as “Tell, Don’t Ask”.

Here Are Two Examples, one where I violate the Law of Demeter, and another where I don’t.

I wrote this code recently, in flagrant violation of the Law:

cookies[:store_id] = @login.store.id

Suppose @login is not an ActiveRecord object, it does not automatically have a #store_id method. Should I create a delegator for this?

class Login
  def store_id
    store.id
  end
end

This is pretty silly. The store_id is not an attribute of the login; rather it’s an attribute of the store, and the store is an attribute of the login. The delegator is needless code cruft to replace a dot with an underscore, it smells of the endless boilerplate Java code of my youth. Demeter be damned.

On the other hand, here is a refactoring I did, incidentally complying with the law of Demeter:

Here is the original, Demeter-violating code:

def find_attribute_given_name(name)
  attributes.detect { |a| a.name_or_alias == name }
end

The call to == here is the violation of Demeter. I later replaced this with:

attributes.detect { |a| a.named?(name) }

The latter complies with the “law”. And it’s much better code. But was I lead to the improvement to this by Demeter? No, I was lead to it by a better understanding of the encapsulation boundaries of the object (#name_or_alias became private) and by a desire to have my code be more terse and clear. a.named?(name) is the most terse explanation of the intended computation that I can think of.

Demeter be damned.

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 5/7/2008

Pivotal Labs
Wednesday, May 7, 2008

Interesting

  • “JsUnit has been updated” A new feature available on trunk is a “modern” ui. The modern ui has 2 panes, with errors and failures listed on the left. Clicking on an error displays details on the right. Get this by 1) installing trunk, and 2) specifying “ui=modern” as a parameter.

Screen grab of old and new ui for jsunit

Ask for Help

  • help: “Selecting an iframe with Selenium RC fu“ It appears that selenium has the ability to select an iframe, but it doesn’t work in selenium rc fu.
    • answer: Apparently the selenium provided in selenium rc fu is an older version
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 5/6/2008

Pivotal Labs
Tuesday, May 6, 2008

Ask for Help

  • “RSpec only Hoe?” How do you convince Hoe to only run specs and not tests?
    • In the distance a dog barks…
  • “Is there a fun way to learn Ruby?” I have a 500 page book, but is there a fun way to learn Ruby?
    • (various pivots look at each other) “Pairing…of course”
  • “Use RSpec to verify deployment?” Previously I used Rspec with JRuby to create stories for a java project. It would be nice to have a deployment story. The plan would say “apache goes here”, “mongrel goes there”, “mysql goes over there”. You could show the plan to your customer printed on a piece of paper and you could run it to do the deployment.
    • Capistrano can provide some of this vision.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Standup 05/05/2008

Pivotal Labs
Monday, May 5, 2008

Interesting Things

  • Interesting things were all about internal Pivotal Labs activities this week.

Ask for Help

  • Window.frames['name'] If we add a frame, then remove it, then add it again, it’s still there.

    • [silence]
  • “What’s that gem that draws a graph of your models?”

    • RailRoad

Sample RailRoad Output, model names changed to protect the innocent

  • 0 Shares
  • Share on Facebook
  • Share on Twitter
Pivotal Labs

Screw.Unit 0.3

Pivotal Labs
Sunday, May 4, 2008

Screw.Unit 0.3 is now available. I’m skipping 0.2 since there have been two milestones worth of additions to the new version of Screw.Unit. New features:

  • test suite functionality
  • afters
  • global befores and afters
  • enhanced error messages
  • new matchers

Here’s how to do a global before:

Screw.Unit(function() {
  before(function() {
    $("#screw_unit_content").html("");
    ActiveAjaxRequests.length = 0;
    PainPoint.instances = [];
    delete window._token;
  });
});

Put this, for instance, in your spec_helper.js file.

Here is how to manage a suite:

Have a suite.html file that requires the Screw.Unit source, your spec helper, and your various test files.

<html>
  <head>
    <script src="../lib/jquery-1.2.3.js"></script>
    <script src="../lib/jquery.fn.js"></script>
    <script src="../lib/jquery.print.js"></script>
    <script src="../lib/screw.builder.js"></script>
    <script src="../lib/screw.matchers.js"></script>
    <script src="../lib/screw.events.js"></script>
    <script src="../lib/screw.behaviors.js"></script>
    <script src="spec_helper.js"></script> <!-- SPEC HELPER -->
    <script src="behaviors_spec.js"></script><!-- A SPEC -->
    <script src="matchers_spec.js"></script> <!-- ANOTHER SPEC -->
    <link rel="stylesheet" href="../lib/screw.css">
    </head>
    <body></body>
  </html>

Have a test file (for example, matchers_spec.js):

Screw.Unit(function() {
  var global_before_invoked = false;
  before(function() { global_before_invoked = true });

  describe('Behaviors', function() {
    describe('#run', function() {
      describe("a simple [describe]", function() {
        it("invokes the global [before] before an [it]", function() {
          expect(global_before_invoked).to(equal, true);
        });
  ...
});

Extra Powerful Matchers

// hash equality
expect({a: 'b', c: 'd'}).to(equal, {a: 'b', c: 'd'});

// recursive array equality
expect([{a: 'b'}, {c: 'd'}]).to(equal, [{a: 'b'}, {c: 'd'}]);

// regular expressions and string inclusion
expect("The wheels of the bus").to(match, /bus/);
expect("The wheels of the bus").to(match, "wheels");

// array emptiness:
expect([]).to(be_empty);
expect([1]).to_not(be_empty);

Thanks To

  • Brian Takita for the ideas and much of the implementation.
  • 0 Shares
  • Share on Facebook
  • Share on Twitter

Topics

  • agile (780)
  • rails (113)
  • testing (88)
  • ruby (83)
  • ruby on rails (70)
  • jobs (62)
  • javascript (55)
  • techtalk (44)
  • rspec (38)
  • ironblogger (32)
  • productivity (30)
  • activerecord (29)
  • gogaruco (29)
  • git (28)
  • nyc (27)
  • rubymine (26)
  • bloggerdome (23)
  • mobile (22)
  • process (21)
  • pivotal tracker (20)
  • cucumber (20)
  • jasmine (19)
  • design (18)
  • ios (18)
  • webos (17)
  • objective-c (17)
  • android (16)
  • palm (16)
  • "soft" ware (16)
  • fun (15)
  • tracker ecosystem (15)
  • ci (15)
  • cedar (15)
  • rails3 (14)
  • performance (14)
  • bdd (14)
  • gem (13)
  • css (13)
  • tdd (13)
  • selenium (12)
  • goruco (12)
  • bundler (12)
  • meetup (11)
  • railsconf (11)
  • nyc-standup (11)
  • capybara (10)
  • mac (10)
  • mojo (10)
  • chef (10)
  • api (10)
Subscribe to Community Feed
  1. 1
  2. 2
  3. →
  • About
  • Case Studies
  • Team
  • Community
  • Careers
  • Contact
  • Labs
  • Events

Contact Us

contact@pivotallabs.com
+1 415-77-PIVOT
TwitterLinkedInFacebook

Pivotal Tracker

Tracker is the award-winning agile project management tool that enables real-time collaboration around a shared, prioritized backlog.
Visit pivotaltracker.com >