Developing TDD through Microskills
A few weeks ago I attended a Craft Conference workshop spanning two days on Test Driven Development with Llewellyn Falco. It was refreshing to be at such a hands on session at a conference, to be learning by doing.
In this post I will focus on some key learnings gained from the session on microskills within TDD, and how I’ve applied these to my everyday development.
The first thing we did in the workshop was to pair. However, before pairing Llewellyn gave us a pairing 101 on the importance of sticking to roles, and the value of applying a structured communication technique.
Sticking to frequently swapped roles is more efficient
Stick to strict driver/navigator roles, not because it’s arbitrary, but because trying to do two things at once is difficult. Rather than trying to simultaneously type and figure out what test you’re writing, Llewellyn encouraged focusing on a single task at hand (i.e. communicating effectively as a navigator, or being the most efficient driver by using shortcuts).
Be sure to consciously communicate with your pair, to help them understand what you’re trying to tell them. There are three key steps to communicating:
- Intention (e.g. I want you to find my concealer)
- Location (e.g. It is in the bathroom cupboard))
- Details (e.g. It is in a red case with the word ‘concealer’ on it)
The aim of this is that you begin with Intention, then if more information is required can go on to location, and then if that’s still not enough go to the details. Following this pattern allows for the driver to take more direction as and when necessary. Once you’ve gone through this scenario once, you’re unlikely to need to do it again, as the driver would have all the information to hand to repeat the steps.
Writing an individual test is a skill composed of four core microskills: Whiteboard, English, Code and Result. Using this structure focused the act of writing tests based on specific scenarios, rather than abstract rules.
Focus on scenarios not rules
I found focusing on a scenario to be a much more effective way of figuring out what behaviour the API should have. Scenarios should be things that happened in past, not in the future. They work with specifics and have no ambiguity. When writing tests talk about scenarios and examples, not about rules.
Draw out the problem to gain a shared understanding of a specific scenario. Storyboarding can be an effective way of communicating more complex scenarios.
Write out each step of the test in your native language (in my case English), this felt almost like story tasking but for an individual test case. The key point to this stage is that it forces the scenario to become specific (e.g. specifying the frame and score) and set in past (nothing can be ambiguous).
bowling kata first test
start a new bowling game
roll the scores [0, 0]
check the score for the first frame is [0, 0]
Once you have the English (or your native language), you can then write the code that matches the instructions step by step. Once this process has been completed the instructions can be deleted out and you have the test.
BowlingGame game = new BowlingGame();
Verify that the result matches the expectation. In our workshop we explored using Expressive Objects and an assertion library to verify results. Llewellyn advocated having objects print their data, to make data easy to reason and state verifiable. This could be as simple as having an object printed as a string, to be used in test cases but also to enable logging of object state.
Naming is a continuous process
Something that was especially interesting for me was the concept of naming being a continuous process, as outlined by Arlo Belshee’s 7 stages of naming. I like this because often I try to find the perfect name on the first attempt, but this diagram shows that actually naming is a process.
An important part of the development process is to ask how the name can change, rather than why it shouldn’t change. Naming and refactoring is a continuous path, and this idea of the 7 stages highlights the importance of taking every opportunity you can to improve it.
This in turn feeds into the idea of slow change, which is gradual change that may not be immediately obvious but overtime causes enormous change.
We wrapped up the session by acknowledging the difference between practice and performance, and how the code we write in our day job is performance. However, performing is different to practice or learning. An analogy was drawn between a musician performing in an orchestra, and how the most important part of that performance were the many hours of practice prior to it.
As a developer we need to acknowledge the importance of deliberate practice, and the difference between being in a state of learning vs a state of performing. It’s really important to carve out time to rehearse for the daily performance.
The overall motto… spend more time learning at work. Try to spend at least one hour a day (in or out of work) on deliberate practice.
Since attending the workshop I’ve experimented with applying the testing cycle in my day to day pairing. The feedback that I’ve had so far is that this was an especially useful process to apply to complex test scenarios, such as Service tests.
Personally, I’ve found using the microskills of the testing cycle have enabled me to more quickly write a concrete test scenario. I previously often fell into the trap of trying to figure out all the rules up front before feeling able to write the first test. I’ve found it very helpful to have a strategy for writing tests, and have been using it in my personal projects as well as with my pairs when they have been open to it.