I am working for a large company that has selected agile as our working ideology. In practice it means that we all follow the Agile Manifesto principles, but inside the teams we are quite free to select our ways of working. While some teams absolutely love agile some teams deeply dislike it. I enjoy being agile, but I during this sprint I have learned in the hard way why some teams don't. I would like to share my experience with you.
Let's Try Pair Programming
Some time ago our little Java team was merged with another scrum team that works on the same functionality area but mainly uses Bash and Perl. In this sprint all the tasks landed to the code unfamiliar to the previous Java team members. As we have good experiences on pair programming we suggested it would be a good way to get familiar to the code and get the tasks done. We were wrong.
The Pair Programming Just Did Not Work
We tried pair programming with different tasks and different people. To our surprise it was not fun anymore and it was not effective. It seemed to slow down the work, confuse people, mix up responsibilities and decrease the quality. Some people felt that they did not get enough keyboard-time while others felt that they would have been faster on their own.
At first I was dumbfounded, but then I paired with a colleague from my previous team. We had previous experience of pair programming together, but we were suddenly having similar problems too. What had gone into us? After a while I realized what we did differently.
Test Driven Development (TDD) Was Not Used
What has TDD or any other XDD-method to do with pair programming? A lot actually. After this experience I believe that test/behavior driven methods enable pair programming. However I looked at the problems that we had, I realized that we would not have had them if we were using TDD. Here are some examples.
How Do I Know What He Is Thinking?
"Doesn't that regular expression leave out the thing X that we want now?", I asked from my pair. "Oh no, I'm not parsing that thing right now. I'm parsing Y first then we do X", he replied.
I could see the method names and knew what the code did when he got it ready, but I could not see my pair's intentions and reasons while he was doing it. Of course I could always stop him and ask, but then he would need to stop and cut the flow to explain me. And when I did stop him, we ended having long discussions about how the task should be implemented.
TDD - "Let The Red Light Guide You"
If we had been doing TDD, we would have started by running the test cases. There would be a red one, the failed case he would be fixing. I would read the title of the case and know immediately what we wanted the new code to do. He would open the test case and I would see what aspect of the code is failing. Then he would just move to the code and start fixing it. No lengthy discussions. And if I would have been there from the start, I would have seen how he constructs the test case and that would tell me even more about his intentions.
Is It My Turn Already?
I had been watching him code quite a while. I had already asked for my turn five minutes ago, but he just did not get it to stop. I could see that he had something more in his mind that he was afraid he would forget if he would not do it right away and he mumbled something along. So well... I could wait...
And when it was my turn, I noticed I did the same thing - we all did. We suddenly did not want to let go of the keyboard.
TDD - Each Test Is a Mini Task
That says it, doesn't it. Natural stopping places and a reminder to tell you what you were about to do.
What Did You Do Last Evening?
We have different working time preferences and we have meetings that cut the day every now and then. That means we either need to pause the task or program on our own. With our schedules pausing is not a real option. So whenever either one of us jumped in, the other had to explain what s/he had done in the meantime. Whenever one of us was leaving, we had to make deals again. At the end of the task we were really confused about what got done and what did not.
TDD - "Let The Red Light Guide You"... again
We were programming when my pair said he needed to go somewhere. "I'll be back in an hour", he said. "Well, I want to leave right after four today, so I won't be here when you will come back", I replied. "No problem, you can just continue here on my PC. Lock it when you leave", he said. And so we did. He came back later on that evening and picked up from where I left. Next morning I came to work early and continued where he left. That is something you just cannot do without tests that you can easily run to see what all has been done and where.
It is a good practice to leave a failing case behind when you have to stop working (but cannot commit to version control yet). That way your pair (or yourself) will know where to continue.
How Do We Know Everything Is Done?
Well, we are supposed to try it out, of course. But we are being agile now so there is no test plan. For some reason these tasks are not of small size either, like they used to be. We tried out the code and it seems to work, but I'm not really sure it if does all that is needed. I'm not feeling confident about this code. Maybe we just forgot to implement something.
TDD - Test Is The Specification
How we used to solve this was to implement two levels of test cases to guide us.
While I would be implementing the functionality our architect-tester would implement the higher level test cases for it. Higher level test cases describe the wanted behavior. Sometimes we planned the tests together in the start of the sprint and the test steps became implementation tasks that we then put to our backlog. In best sprints he worked a bit ahead and the test was ready before we started the actual programming.
Did you notice how I said "Architect-Tester"? As the tests are the specification, tester and architect roles merge nicely in TDD. Esko Luontola has written a blog article about this way of thinking: TDD is not test-first. TDD is specify-first and test-last
The lower level test cases would be the module test cases that we did while programming. They provide us a list of mini tasks that got done.
Then we could just look at the module test case list and see that everything is done, and run the higher level test cases to see that the functionality as a whole does what was wanted.
Why Don't We Use TDD Then?
When we started working with Bash and Perl code we asked the rest of the team why TDD was not used already. We were told that it had been tried and found unsuitable. We were stubborn enough to try again and quickly found out we agree.
What you need for TDD:
- Time to learn the method
- Good code refactoring tool
- A language that supports encapsulation
- Continuous integration that runs your tests every time code is changed
- Testing tool/framework
What, you thought you only need the testing tools and the right attitude? That is true for your home projects where you are the only one touching the code and it does not need to be maintained for years by several different people. But for the large scale commercial projects the situation is different.
First and the most important showstopper is often time. Learning to use TDD fluently takes about a year and during that time you will be slower than normally. If we had not known the method already, we could not have even tried it - the schedules are just too tight!
The biggest showstopper in our case was the missing code refactoring tool. Refactoring in TDD is not an optional step and it is needed especially in the test part of the code. You will find that you often need to pull common parts away, combine or split methods, rename and move stuff. One of the best things in TDD is that it helps you to create code that is very easy to refactor. If you cannot refactor, you will end up with fragile and unmaintainable code base.
If you are working with old code that was not done with TDD, you need to make the code testable before you can work with it using TDD. That is done by carefully refactoring parts of the code so that you are able to test just the part that you need to change. If you don't have tools that let you refactor code safely and effectively, you just cannot use TDD.
TDD also makes the code highly modular and it quickly becomes unmanageable if you cannot hide the parts that are not meant for public use. I can guarantee you'll end up with "ravioli-code" if you don't have encapsulation.
If you have more than one person working with the code then you need your test cases to be run automatically every time you change the code. Otherwise you'll find that someone has made changes to the code, broke the tests and now you'll have to fix them.
Previously I thought test framework a must too, but most programming languages have features that enable testing them. But that must be mentioned anyway and a good testing tool makes the process more fun.
The common understanding seems to be that agile offers us a box of tools and methods from which we can just cherry-pick the best for our purposes. Now I understand that agile tools have dependencies to each other. What tools become unusable if we don't select certain others?
I think TDD is an enabler for lots of things in addition to pair programming. Are your scrum meetings a chore and feel unnecessary? Maybe your tasks don't connect to what you are actually doing?
I think that there is a real risk of just playing an "agile show" if TDD is not used. You look agile outside, your builds automated and green, but what happens inside is not agile at all - it is just a show with no go. It will feel stupid and unmotivating to the people who are forced to play the show.