Shy Tests and Ping Pong Pair Programming

Jared Carroll ·

I’ve had my best pair programming experiences using a technique called ping pong pair programming. This is a back and forth style of development that goes something like this:

  • pair A writes a test
  • pair B makes it pass
  • pair B writes a test
  • pair A makes it pass

This is a great way to get both pairs actively involved, each getting time writing tests and implementation. However situations can arise when the pair writing the test makes too many assumptions about how to make the test pass. The other pair is then left with no choice of implementation. In my opinion this isn’t true collaborative development because one pair, by dictating implementation, is in essence playing both roles. In order to give your pair more freedom in their implementation (and in general want people to look forward to pairing with you) here a few tips on keeping your tests, with respect to implementation, shy.

Constants

Avoid referencing constants.

describe '#valid?' do
  context "given a document with a title longer than 160 characters" do
    before do
      title = 'a' * (Document::MAXIMUM_TITLE_LENGTH + 1)
      @document = Factory.build(:document, 
                                :title => title)
      @valid = @document.valid?
    end

    it 'fails' do
      @valid.should_not be
      @document.should have(1).error_on(:title)
    end
  end
end

When you run this test you’ll get a unitialized constant error. That is, you have an error instead of a failing test; this should be an indication to you that this can’t be the right way to develop. Besides is Document::MAXIMUM_TITLE_LENGTH really the best implementation?

Instead, replace the constant (Document::MAXIMUM_TITLE_LENGTH) with its value and then see what your pair comes up with.

describe '#valid?' do
  context "given a document with a title longer than 160 characters" do
    before do
      title = 'a' * (160 + 1)
      ...
    end

    ...
  end
end

Named routes

Avoid using named routes in integration tests.

  context 'Given invalid credentials' do
    before do
      @email = ''
      @password = ''
    end 

    it 'Then I can not log in' do
      visit login_path
      fill_in 'Email', :with => @email
      fill_in 'Password', :with => @password
      click_button 'sign in'

      page.should have_content('Invalid email and/or password')
    end
  end

login_path may be a pretty standard named route when it comes to logging in but maybe your pair has something else in mind.

Instead, replace the named route with a String.

  context 'Given invalid credentials' do
    ...

    it 'Then I can not log in' do
      visit '/login'
      ...
    end
  end

Mocking

Since mocking is essentially specifying implementation, it can be harder to keep your tests shy when mocking. The key is to mock at the lowest level possible.

Here is a nosy test for a feature that involves sending an HTTP request.

describe '#save' do
  context 'on create' do
    it 'notifies its project via a webhook' do
      project = Factory :project
      HTTParty.
        expects(:post).
        with(project.webhook_url)

      milestone = Factory.build(:milestone,
                                :project => project)
      milestone.save!
    end
  end
end

This test gives your pair no leeway when it comes to choosing an HTTP library. Instead, refactor and mock at a lower level. In this situation we can use the excellent HTTP mocking and stubbing library webmock.

describe '#save' do
  context 'on create' do
    it 'notifies its project via a webhook' do
      project = Factory :project
      stub_request :post, project.webhook_url

      milestone = Factory.build(:milestone,
                                :project => project)
      milestone.save!

      WebMock.should have_requested(:post, project.webhook_url)
    end
  end
end

Be fair to your pair

All of these examples reduce the coupling between tests and their implementation in order to support true collaboration. The first 2 situations (constants and named routes) might seem like not being very DRY; however I prefer to trade off that duplication in favor of less dependencies and a better pairing experience for both pairs.

When developing solo you often already know how you are going to get a test to pass before you even write the implementation because you have a personal style and certain libraries you like to use. I find ping pong pair programming to be the most productive and enjoyable development style for both pairs. When your pair has complete freedom of implementation you get to see their true style and technique, information and knowledge flow more freely, and most importantly each pair usually ends up learning something from the other pair.