TDD as if You Meant It - My turn!
This was originally written on my old website, www.melanieburns.net, and published on Dec. 9, 2019.
Test Driven Development (TDD) that has been around for a while, and was first introduced to me during college. Its basis from Kent Beck described in “Test Driven Development: By Example” is made up of these simple rules:
Add a little test
Run all tests and fail
Make a little change
Run the tests and succeed
Refactor to remove duplication
Now to be clear, I haven’t had a lot of hands on experience with others working in this paradigm, and it’s something I’ve really wanted to learn more about. To me it’s a bit of a brain flip, but I can see how it can assist in testing, methodology, and, I think, feature creep. Yet, what I have seen looks so much more like the Pseudo-TDD described on the blog cumulativehypotheses’s article TDD as if You Meant It where the design is implemented in the developers imagination long before an initial test is written. They go on to discuss how this creates a long design process that isn’t getting incremental feedback and moving the design decisions before we even truly know what the problems are, erasing some of the value of the simpler TDD idea.
TDD as if You Meant It goes on to describe an exercise of building a game of tic-tac-toe using a distinct set of rules that push the participants to really follow TDD and push developers thought process of what this might look like in a daily work flow.
Myself and three of my colleagues decided to take on this exercise ourselves as programming mob for 2 hours. Our mob consisted of one laptop shared to a conference room screen. We had one keyboarder who could type but not think, one navigator who told the keyboarder what to type and made all decisions, and two brains that who gave thoughts and insights to the navigator. These roles were rotated every 5 minutes allowing everyone to have chance to have decision making. If you’re curious on this developmental method check out this article here.
We defined our business goal as:
“For tic-tac-toe, given a board state, we say whether X won, whether O won, or if there is no winner. “
We attempted to follow TDD as if You Meant It ’s rules as defined below:
Write exactly one new test, the smallest test you can that seems to point in the direction of a solution
See it fail
Make the test from (1) pass by writing the least implementation code you can in the test method.
Refactor to remove duplication, and otherwise as required to improve the design. Be strict about using these moves:
you want a new method—wait until refactoring time, then… create new (non-test) methods by doing one of these, and in no other way:
preferred: do Extract Method on implementation code created as per (3) to create a new method in the test class, or
if you must: move implementation code as per (3) into an existing implementation method
you want a new class—wait until refactoring time, then… create non-test classes to provide a destination for a Move Method and for no other reason
populate implementation classes with methods by doing Move Method, and no other way
The member of the pair without their hands on the keyboard must be very strict in enforcing these rules, especially 4.1 and 4.2
Insights
Although we haven’t gotten into the process yet, our insights need no further explanation than what is above, and knowing them as you read through our process should help point out places we could have gone in different directions.
This was shockingly difficult and frustration.
We definitely modeled Gojko Adzic’s first methodologies for this challenge with Go discussed here.
Trying this challenge without a facilitator made it hard to push back our own patterns of thinking to truly discover the opportunities here.
I was impressed with the test code ended up being modular, easy to read, and functional.
I felt that we got a little stuck in the Pseudo-TDD on accident, and would like to revisit this.
Whether it was from that or not, we all thought we missed some of the learning opportunity and would like to revisit.
More on the mobbing process - the condensed knowledge share from mobbing together was extremely valuable to me.
In the end we ended up with the very design most of us went into it thinking we would. Now that could be because of the bias we had about the design going in. It could also be that the example was a bit too simple allow for an easy mental design to pop into our head. Trying it again with something more difficult may help keep the automatic design from occurring.
Process
What We Were Using
Window’s PC
C#
Visual Studio
Resharper
NCrunch
Fluent Assertions (a nuget package)
What Did We Do
We ran through the exercise without planning on sharing an article like this so I do not have the code to give a true step by step. I’ll put examples in from a couple key points I remember, and the code we got to after the two hours.
We started out with an empty unit test that we made sure NCrunch would instantly test and fail. Then came time to pick the first test which looked something like this:
[TestMethod]
public void GivenAnEmptyBoard_ThenThereIsNoWinner(){
if(board.isEmpty())
}
Whoops! Already broke a rule. In this case it was already having a design in our head about a board object to convey the tic-tac-toe board. We put it in the test assuming an implementation would be built out of the test. So we deleted the whole thing.
Next up we went for truly smallest change we could think of. A 1x1 board.
[TestMethod]
public void GivenAnEmptyBoard_ThenThereIsNoWinner() {
var board = new[,] { ‘ ’, ‘ ‘});
var winner = board[0,0]
winner.Should().Be(' ');
}
This passed! It felt frustratingly simple, but it does put us one tiny step closer. We created a new test case for X and another for Y and got them passing. At this point there was some clear duplication. We decided to extract a method.
public char FindWinner(char [,] board){
return board[0,0];
}
Again this passed. My goodness was it frustrating though. This felt like it could never help the final goal. We knew this was meeting the business goal at a 1x1, but that’s not what we were aiming for eventually. Yet the point of trying this isn’t to be stop when it’s frustration or you’re lost. It’s keeping going to see what values can be gleaned. This was clearly the uncomfortable moment where we were having to go against our preconceived notations. It’s also worth to note that the entire code got deleted a couple more times in all this, but I don’t remember at which points.
Alright. We have the 1x1. It met the goals. It passed tests. The duplication had been extracted to a method. It was time to move on. After some debate we went from a 2x2 board, deleted that, and continued to the actual goal - a 3x3 board.
We created a test that failed by having the winning condition be X’s along the middle column. The code was still looking at just [0,0] box though! Uh oh. Given we needed to build in the smallest solution we in lined the following solution.
public void GivenABoardWithXFillingTheCenterColumn_ThenXWins() {
var board = new[,]
{
{'O', 'X', ' '},
{' ', 'X', ' '},
{' ', 'X', ' '},
});
var winner = board[0, 1] == player && _board[1, 1] == player && _board[2, 1] == player;
winner.Should().Be('X');}
This clearly only works for this test case, but we went through the process trying to follow the rules. Continuing on to make test cases for columns, have it fail, make something similar. To be honest, this was tedious and annoying. We all knew where this was headed from this point of iterating through the loop. We extracted the winner line above, and created a new FindWinner that loops through each of the columns. As shown below along with the board class it eventually got moved into throughout the refactoring stages.
private bool IsColumnWinner(int c, char player) {
return _board[0, c] == player && _board[1, c] == player && _board[2, c] == player;
}
public char FindWinner() {
for (var c = 0; c < _board.GetLength(1); ++c) {
if (IsColumnWinner(c, 'X')) {
return 'X';
}
if (IsColumnWinner(c, 'O')) {
return 'O';
}}
return ' ';
}
Alright. Everything passes again…. as developers we know it’s far from over. We continued on for a bit, but the completing the rows was exactly the same series of events. We did our best to restrain ourselves, follow the rules, and go through the process. Yet, we weren’t feeling value added at this point and our time was about to end. Although this restrained process to the very for loop we were originally planning was frustrating… it was interesting to see how the it was starting to shape the code step by step.
In the end, this was definitely interesting. As discussed in the insights we don’t know if we enabled ourselves to truly see the value of this process given the scope of the example and our immediate creation of the board.
A huge part of the learning experience here had little or nothing to do with this expertise. One of my colleagues has incredible Visual Studio and C# skills. It was awesome to watch how quickly he could make large, safe refactoring and extracts with tools. I thoroughly enjoyed learning some neat new hot keys and just enjoyed the learning experience of sharing knowledge among each other.
Next Steps
Sometimes these exercises end with this type of article cause it wasn’t a great fit, or sometimes it get’s worked into the daily flow. This one sits between those two where I don’t want to add it to my daily workflow yet, but I also feel like there is more to understand. The following are where I hope to go next.
I definitely want to revisit this again maybe with a different problem.
I would like to approach it again with a mob for knowledge share, but also on my own. I am autistic and my audio processing can make it difficult to have a group setting and optimally learn.
Other write ups such as this were useful before and during this exercise, they were also unsatisfying. I’d like to record or live stream going through this process so people are able to truly see how many times code got deleted, changed direction, and rules broken. In this case, especially without a facilitator, seeing other’s struggles would have been helpful.
Get more comfortable with TDD to truly understand its pros/cons for use in my daily workflow.
Others
There is a great list of other projects at the bottom of TDD as if You Meant It.