In modern software development there are three development practices that everyone should strive to apply:
- Automated testing
- Pair or mob programming
- TDD, test driven development
After many years of using and researching these practices in the development community there is no longer any question whether these engineering practices bring value or not – they do. It’s not a matter of opinion, it’s a matter of fact. We know that now.
How to implement a practice
Still, adopting new habits is hard, so in many organizations I visit some of these practices are not (yet) applied.
It’s not enough for management to just inform people about the practices and expect them to change their ways of working. The teams and the team members have to take responsibility themselves to change in a deliberate way and management has to allow them to do this:
- As a development team, choose one practice to implement
- Decide to take an incremental & iterative approach to train yourselves to apply the practice
- Decide on a “definition of doing” for the first level of applying the practice. What is the visible proof that you are actually changing your ways of working?
- Super important: Create a metric/indication and a visualization of it for your own team. Metrics don’t have to be exact, but need to show the difference between not applying and applying the practice.
- Track your usage of the practice as a team using the visualization on a daily basis.
- When you have reached your “definition of doing”, go back to step 3 and select the next level of applying the practice – or go back to step 1 and select another practice.
It’s important to do this bit by bit with some visible proof that you are getting somewhere. Otherwise it’s easy to lose traction and not make any progress. Don’t forget to celebrate your achievements!
Now, let’s dive a little bit deeper into each of the three “no brainer” practices:
It’s impossible to be agile without automated testing. When releasing frequently (every week or day), the test burden becomes overwhelming if you test manually. Or alternatively, you would not have sufficient test coverage, usually resulting in poor and deteriorating quality over time.
Focus on three levels of testing:
- Unit tests
- Integration tests
- System & acceptance tests
When writing new code, unit tests are the foundation. Aim for 80% code coverage on the unit test level. This prevents regressions and allows you to refactor your code with confidence. Make sure you have at least one acceptance test for every acceptance criteria in your user stories. This ensures that the application can be used as intended. Create integration tests as needed.
When working with legacy code (i.e. code not already covered with tests) adopt an outside-in approach combined with the scout rule.
Start with covering the behavior of the application (business rules) with acceptance and system tests. Do it throughout the entire application. This is the outside-in approach.
I addition, whenever you modify existing legacy code, you cover that part with unit tests before going ahead. The good thing about doing it this way is that you can refactor the new and modified code with confidence before finishing up. This is called the scout rule (leave the legacy code in better shape than it was before you touched it).
The best metric to track when introducing automated testing is code coverage. A good goal to aim for is around 80% branch coverage, but when teams start out they are often in the single digit domain. Don’t despair, though. Just, set a rule that code coverage is never allowed to drop below its current level (whatever that is at the moment). This means that you don’t add new code without coverage and that any old code you touch must be put under test.
Pair or mob programming
If programming was about typing text, pair programming and mob (group) programming wouldn’t make sense. You can type more text if everyone is doing it simultaneously instead of sharing one keyboard and a screen.
Programming, however, is about problem solving. It’s a stream of small problems that need to be solved in order to figure out which code to write in order to make the software behave the way we want.
Problem solving is best done in pairs or in groups. We simply solve problems faster and better when we have others to discuss them with – “two heads are better than one” as the saying goes.
Pair programming has existed for nearly 20 years. Research shows that pair programming:
- Is faster. You solve problems and write code in less time.
- Has lower bug density. You make fewer mistakes thanks to the super fast feedback loop.
- Enables learning and cross training.
- Generates better/cleaner code that is easier to maintain.
There is overhead involved when more than one person shares the same keyboard, but you’ll soon start reaping the benefits in the form of higher delivery speed, better quality, less maintenance and the fact that you don’t have to perform code reviews (the code has already been reviewed in real time).
The book “Toolbox for the Agile Coach – Visualization Examples” suggests using a “Promiscuous Map” to track the number of times per week pair programming occurs.
TDD, test driven development
TDD, test driven development, has very little to do with testing. It’s a technique for writing code.
The goal is to develop code by using test cases to help you figure out what code to write next. Whenever you clean up (refactor) code, the test cases help prove that the modified code behaves equivalently to the old code. This provides confidence and courage, making refactoring a natural part of programming.
Traditional TDD works primarily on the unit level and its three phase cycle is very fast (minutes):
- Red: Before writing any production code, figure out how you want the first piece to behave and write a test case that verifies that behavior. When you run the test case it will fail (show red) since there is no production code yet.
- Green: Write production code that satisfies the test case and makes it run green. It’s OK at this stage to have a non-optimal implementation.
- Refactor: Once you have a working implementation, clean up the code to your engineering standards while continuously running the test case every step of the way to ensure it keeps running green.
When the cycle is complete, start over again defining the next behavior your function/method should have.
This style of programming means that you write your unit tests in parallel to your production code.The unit tests verify that the pieces of code you write behave as you intended.
Adopting TDD is hard for some people, since it involves changing your programming style. Nevertheless TDD pays off richly, since it:
- Has lower bug density. You make fewer mistakes.
- Generates better/cleaner code that is easier to maintain.
- The test cases act as living executable documentation.
- Automatically covers your code with unit tests.
- Introduces the habit of safe refactoring, which in itself increases code quality immensely.
Common ways to track the introduction of TDD include measuring unit test coverage (fits well with the test automation practice) and simply confirming its use as part of a team’s Definition of Done during stand-ups when moving backlog items to the Done column.
Stop accepting inferior engineering practices. It’s a matter of professionalism (not opinion or preference) to apply the basic practices of automated testing, pair or mob programming and test driven development.
I know these are not the only practices on the planet, but they are a good foundation for any development team and will lead to higher motivation and pride in your work, better quality, tighter feedback loops and pretty soon also higher speed.