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:
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.
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
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
context 'Given invalid credentials' do ... it 'Then I can not log in' do visit '/login' ... end end
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
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.