I have been doing solo .net programming for a bit over 4 years, and most of the unit testing I’ve done was post-code unit testing.
I’m quite fresh in matters of full TDD as I only started experimenting with it in the last year or so, and I also couldn’t say I’ve had successful close encounters with pair-programming before.
But recently I have had the opportunity to try both of them full on, both at once! And because of the overall positive experience I’ve had with it, I decided to write a little about it.
Ping pong programming, also known as P3 is a crossbreed between the two extreme programming practices pair-programming and test-driven development. It is most certainly not new, as one can find by asking google about it. Personally I was lucky enough to experience this first hand as the method is employed occasionally at my work-place.
How to do it ?
Optional: At session start define a task to do, no more than 1 hour or 2 long.
The P3 iteration
- Jane becomes the driver and writes a test. It is of essence for the test to be as fine grained as possible. Meanwhile Jim is the observer. Ping !
- After Jane wrote her test, Jim takes the drive and writes an implementation that passes that test, but nothing more. Thus Jane becomes the observer and the roles switched.
- Both programmers look into ways to refactor the code / do clean code / remove duplication.
- Jim continues by writing the next test, as small as possible. Pong !
- After Jim wrote his test, Jane takes the drive and writes the minimum implementation that passes the test and nothing more. Roles switched again.
The whole process could be regarded as a game in which the programmers can compete in writing the smaller unit tests, and can cooperate in refactoring the code, but be wary of competing in refactoring the code.
Of course the process can be tweaked to suite your own needs and preferences, or to put an emphasis on what factors you consider the most important.
What flavour does TDD add to the mix ?
TDD has lots of benefits to it, but the ones I found the most valuable so far are :
- When I have a sizeable and/or complex problem to solve (plan, implement, test), I usually try to breakdown the problem into smaller units, then try to tackle those based on priority/dependencies.
- The TDD automaton has the intrinsic property to automatically break down large problems into the smallest size of sub-problems that you are comfortable with. The programmer can decide how fine-grained the sub-problems are.
- Imagine you’re trying to implement a non-trivial sized problem, you might find yourself coding various bits of the problem as they cross your mind; but what about when you’re in the middle of a –very- important part of the implementation, when you suddenly realise some collateral small aspect of the initial problem that is also quite important while not being directly linked to what you’re doing right now. Since you don’t want to interrupt the coding sprint you’re currently into, you might make a quick note about the collateral small aspect to be implemented at some point later on, or you might rely on your memory, or you might just forget about it until a time beyond the perfect time to implement it.
- TDD focuses your attention on each micro-problem at a time, and in effect, every small piece of code you make gets your full attention sooner or later. If you came across that small collateral aspect, just write an empty failing test for it, and you make sure you won’t forget to do it.
- When coding non-TDD, I sometimes found myself working on some implementation only to realize that I went ahead and wrote more code that was actually needed, sometimes because I just went with the flow, at other times because I didn’t have a sharp tuned perspective on what the requirements are.
- TDD asks you to write the tests first, and the tests you write are according to the specifications and requirements of that public behaviour you’re trying to implement. This helps tremendously with implementing only what is needed.
- When trying to refactor a piece of code how do you make sure your new changes won’t break current behaviour while also performing new behaviour?
- TDD is a safety net that helps you do refactoring by making sure that you’re previous tests covering the behaviour pass, while the new tests covering your new behaviour also pass.
What about pair-programming ?
- It can increase the amount of ideas / scenarios about things that should happen or that can go wrong with an implementation – directly improving code quality.
- It can increase the chance to spot very subtle problems code.
- It is a means to share implementation knowledge about the task at hand, and general knowledge that is being passed between the two developers (of course this is highly dependent on the difference in experience between the developers).
Having done a lot of reading around the web, here are some collected things to consider when doing P3:
- the driver is the master of the keyboard and mouse.
- should prioritise minimising the time he uses to write his implementation. On the short term this will prevent the observer from becoming bored and loosing focus (as an extreme example, it may be very boring to watch someone write code for 30 minutes straight), so this has a positive impact on both the potential quality of the solution and on the time it takes to implement it.
- can ask the observer for things like implementation ideas, better ways to solve a problem, bringing up alternative ideas, pointing out possible inputs that the code doesn’t cover, suggesting clearer names for classes, members, methods or variables.
- can write code to make his point because sometimes because.. “5 lines of code are worth more than a thousand words?”.
- can do exploratory “see if it works” coding for ~1 minute max.
- should not attempt to bully his way into taking control over the keyboard/mouse.
- is the safety net. He should be on the lookout for potential bugs / larger issues / potential for simplifications and improvements.
- should immediately bring up errors or unreadable code.
- should bring up larger issues preferably after the driver finishes his code writing round.
- should ideally, as needed, tell the driver the missing bit of information or API they need at the moment they need it.
- when pointing out a problem in code, he/she should make sure he does that diplomatically to avoid offending the driver. Examples of good verbal interaction : “do you think this is a valid test?”, “does this look correct to you?”, “what’s next?”.
- It is natural for two programmers to not have the same ideas, this is the whole point behind pair programming.
- Avoid giving subjective reasons to support your arguments like : “I’m right! just because.” / “I’m always right” / “My seniority is higher so I don’t have to give reasons why I’m right”.
- Always give objective reasons to support your arguments.
- Decide what the priorities should be when writing code, is writing Clean Code a priority? Then the cleaner code wins. Is following Object Oriented Design a priority? Then the code that breaks OOD loses.
- As a general rule of thumb try to avoid making personal remarks about your pair-programming mate, as a way to win arguments.
Why mix TDD and pair-programming after all ?
Taking a look at the two practices, the first thing I notice is that some of the benefits of TDD can be further enhanced by some pair programming benefits, or some of its shortcomings might be alleviated, for example:
- The finer grained a problem is broken into, the more rigorous the testing is performed for that problem, so the better the quality of the code.
- If during pair programming, the developers compete to make as smaller tests as possible, the effect would be better code quality.
- If TDD focuses your attention on each small piece of code, at a time (because you write a test for that small piece of code, then you write the implementation itself)
- During pair programming, both developers can focus their attention on each small piece of code at a time, so fewer hard-to-spot problems get missed.
- If TDD contains the step of refactoring during each cycle
- During pair programming, more ideas for a better refactoring might surface from the ideas of the two developers.
- In TDD the more scenarios you test, the greater the benefits are :
- With pair programming, more ideas will surface for scenarios that might be tested.
- In TDD there might still be a risk that one can have a predefined – incomplete opinion about how to solve a problem.
- With pair programming, the resulting code implementation is an inter-weave between both programmers’ perspective on the problem, given the alternative test writing.
- In TDD sometimes the Test/Implementation routine might become too.. routine
- With pair programming, the routine is broken due to the fact you might not expect the test that your partner is writing for you to code.
If done right, P3 has the potential to be a very useful technique to enhance code quality, spread out implementation knowledge throughout a team, while being fun at the same time.
It may not be a technique for everyone, with people who dislike pair-programming potentially having an inertia to also dislike this technique. With this said, P3 does solve some of the annoying aspects sometimes encountered with PP.
I could see how people that dislike pair programming might still enjoy P3.
Is P3 worth it? In my experience it’s the human aspect that’s harder than the process aspect – sometimes this has worked, sometimes this hasn’t. I’m going to use my research and see if the next time can go smoother. For everyone, it’s certainly worth a try – see how it goes for you!