Recently I stumbled across a test driven development article that mentioned something I had not heard before. It's a premise that Uncle Bob came up with as a means to order the priority of the transformations you should apply when practicing test driven development. He called it the Transformation Priority Premise
I wrote a couple small programs using the premise, and really liked the concept he was trying to convey. Though in order to fully explain the premise, we should probably talk about test driven development itself.
So.. what is TDD?
Test Driven Development
TDD is a software development methodology that has three "laws" set forth by none other than Uncle Bob. They are as follows:
- You are not allowed to write any production code unless it is to make a failing unit test pass.
- You are not allowed to write any more of a unit test than is sufficient to fail, and compilation failures are failures.
- You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
These three laws, if adhered to, force you into a cycle commonly referred to as red, green, refactor. Let's demonstrate cycle by writing our own program. First, we'll need some requirements.
This program will return four different responses, and the response will be based on what kind of sentence is used as input.
- If the input ends with a period, return "Ok."
- If the input ends with a question mark, return "No."
- If the input ends with an exclamation mark, return "Quiet!"
- If no punctuation is found, return "What?"
This program is based on an exercism exercise called Bob, which is actually based off of another exercise, Deaf Grandma.
So where to start? The test.
Before we write any production code (the code that will ultimately end up into the compiled binary) we need to first stand up a unit test. To start, we'll need to create our System Under Test (SUT).
[TestMethod]
public void Input_has_no_punctuation_response_says_what()
{
var sut = new Responder();
}
And not all that surprising, the compiler is already yelling at us.
The type or namespace name 'Responder' could not be found (are you missing a using directive or an assembly reference?)
But that's ok! We're already abiding by the first law since we started with a unit test. The compilation error is also expected; the second law states that we can't write any more of the unit test than is sufficient to fail (and compilation errors are failures).
So let's switch context a little bit and start writing some production code.
public class Responder
{
}
We're done! The unit test compiles and passes. The third law forbids us from writing any more production code.
At this point of our development cycle we have gone through red (unit test compilation error), green (adding the Responder
class to make the test pass), and now we're onto refactoring. Heh well, in this case, there's not really anything we can refactor, so we can move on.
With one cycle completed, we start from the beginning again with red. Just like last time, we need to write some more code in our test case so that it fails.
We'll want a method on the Responder
that can take an input of type string
, and we know our first requirement is that if no punctuation is found the result of the method is "What?"
[TestMethod]
public void Input_has_no_punctuation_response_says_what()
{
var _sut = new Responder();
Assert.AreEqual("What?", _sut.Response("Hello"));
}
Now we can go ahead and compile that...
'Responder' does not contain a definition for 'Response' and no extension method 'Response' accepting a first argument of type 'Responder' could be found (are you missing a using directive or an assembly reference?)
Another compiler error. Let's go ahead and fix that up.
We know the compiler error stems from the fact that we never implemented a Response
method on the Responder
class, so that's pretty easy to implement. But what do we write inside of the method body? The answer may seem a little surprising.
public string Response(string input)
{
return "What?";
}
That's right. A constant string value of "What?". Once again, this is because of the third law. We cannot write any more production code than is sufficient to pass the one failing unit test. It may seem a little silly at first, but bear with me, it'll hopefully make a little more sense as we continue writing our program.
Alright, so we've tested the case of no punctuation. Let's move onto a case that includes punctuation, the period. Testing for that gives us a unit test that looks like this:
[TestMethod]
public void Input_is_statement_response_says_ok()
{
Assert.AreEqual("Ok.", _sut.Response("Do it now."));
}
Continuing with the red, green, refactor cycle, we now have a failing test. Let's go ahead and write the bare minimum implementation.
public string Response(string input)
{
if(input.EndsWith("."))
{
return "Ok.";
}
return "What?";
}
}
Easy enough, time for another test.
[TestMethod]
public void Input_is_question_response_says_no()
{
Assert.AreEqual("No.", _sut.Response("Please?"));
}
Next up? You've got it. Let's make this test pass.
public string Response(string input)
{
if(input.EndsWith("."))
{
return "Ok.";
}
if (input.EndsWith("?"))
{
return "No.";
}
return "What?";
}
Now, when we make this test pass, we can see that there is some code duplication going on that we should probably refactor. After all, after making a test pass, we are given the opportunity to refactor the code. Unfortunately, it may not always be clear on how to refactor it. There is hope, however!
The Transformation Priority Premise
As stated in the introduction, The Transformation Priority Premise (TPP) is a premise that was put together as a means to prioritize the transformations that occur when getting unit tests to pass.
When you're practicing TDD you may ask: "How doesn't all code produced by using TDD, just result in code that is specifically tailored to pass the tests?"
You might notice that we're starting to see that a little in our current program. As it stands right now, we have one conditional per unit test. There's really nothing to stop this trend from occurring. There is, however, another little mantra that goes with TDD that pushes developers away from this practice.
“As the tests get more specific, the code gets more generic.”
Put another way: As we add more tests to our system (become more specific), our code becomes more generic (agnostic to the input).
With this in mind, it should be a little clearer to see that our current approach may not be the best one that we can take to solve this problem. We're just introducing more and more if statements to make the tests pass. Let's take a stab at refactoring our code and get away from our potential mountain of conditionals.
To start, the root of the TPP is its list of transformations and their priority. Here is the full list:
- ({}–>nil) no code at all->code that employs nil
- (nil->constant)
- (constant->constant+) a simple constant to a more complex constant
- (constant->scalar) replacing a constant with a variable or an argument
- (statement->statements) adding more unconditional statements.
- (unconditional->if) splitting the execution path
- (scalar->array)
- (array->container)
- (statement->recursion)
- (if->while)
- (expression->function) replacing an expression with a function or algorithm
- (variable->assignment) replacing the value of a variable.
.. and In case you've forgotten, this is the code we're trying to refactor.
public string Response(string input)
{
if(input.EndsWith("."))
{
return "Ok.";
}
if (input.EndsWith("?"))
{
return "No.";
}
return "What?";
}
Now we want to refactor this in order to get rid of the duplication. We started with a single constant, "What?" which was priority #3 and moved onto splitting the execution path, #6. It's time to consult the list and see what transformations we can make in order to clean up the if statements.
Being at #6 currently, the next logical step would be to take a look at #7, scalar to array. That could probably work, but given the context of this problem, we know it's a mapping issue. We're mapping punctuation to results. So let's take it one step further and leverage #8, array to container.
Note: The difference between an array and container is that an array is generally going to be a primitive array (think int[], string[], etc). Whereas a container is going to be something like a List, Set, or Dictionary.
Using scalar to array, and then array to container, we get a refactored method that looks like this:
public string Response(string input)
{
var inputResponses = new Dictionary<char, string>()
{
{ '.', "Ok." },
{ '?', "No." }
};
if (inputResponses.ContainsKey(input.Last()))
{
return inputResponses[input.Last()];
}
return "What?";
}
That's pretty neat. No more repeating if statements. Recompile, ensure the tests still pass.. and they do! Now, there's only one punctuation mark that remains in our requirements, and that's the exclamation mark. We just finished refactoring, so we start again from red and introduce our last test:
[TestMethod]
public void Input_is_yelling_response_says_quiet()
{
Assert.AreEqual("Quiet!", _sut.Response("Woot!"));
}
Going back to our production code, it should be pretty straight forward as to how we can get this test to pass.
public string Response(string input)
{
var inputResponses = new Dictionary<char, string>()
{
{ '.', "Ok." },
{ '?', "No." },
{ '!', "Quiet!" }
};
if (inputResponses.ContainsKey(input.Last()))
{
return inputResponses[input.Last()];
}
return "What?";
}
That's all there is to it! All of our tests pass, and we've met all of the requirements that we set out to meet.
The gain from leveraging the TPP is that it keeps us grounded, and forces us to continue to take baby steps when developing code. We generally do not want to take giant leaps forward. Start with repeating if statements over and over until something like a dictionary or a loop pops out at you.
If you're interested in learning more about all of the nuances of the Transformation Priority Premise, I highly recommend checking out Uncle Bob's initial blog post on the subject which can be found here.