Tuesday, 25 June 2013

How to start with a test, not implementation - part 2

(this post is adapted from my work-in-progress open source TDD tutorial)

(Note that I use "Statement" instead of "test" and "Specification" instead of "Test Suite" in this post)

In the previous part, I discussed how a good name is a great start for writing a Statement when there's no production code to invoke. Today, I'll introduce to you another technique.

Start by filling the GIVEN-WHEN-THEN structure with the obvious

This is applicable when you come up with a GIVEN-WHEN-THEN structure for the Statement or a good name for it (GIVEN-WHEN-THEN structure can be easily derived from a good name and vice versa). Anyway, this method is about taking the GIVEN-WHEN-THEN parts and translating them almost literally into code, then add all the missing pieces that are required for the code to compile and run.

An example speaks a thousand words

Let's take a simple example of comparing two users. We assume that a user should be equal to another when it has the same name as the other one:

GIVEN a user with any name
WHEN I compare it to another user with the same name
THEN it should appear equal to this other user

Let's start with the translation

The first line:

GIVEN a user with any name

can be translated literally to code like this:

var user = new User(anyName);

Then the second line:

WHEN I compare it to another user with the same name

can be written as:

user.Equals(anotherUserWithTheSameName);

Great! Now the last line:

THEN it should appear equal to this other user

and its translation into the code:

Assert.True(areUsersEqual);

Ok, so we've made the translation, now let's summarize this and see what's missing to make this code compile:

[Fact] public void 
ShouldAppearEqualToAnotherUserWithTheSameName()
{
  //GIVEN
  var user = new User(anyName);

  //WHEN
  user.Equals(anotherUserWithTheSameName);

  //THEN
  Assert.True(areUsersEqual);
}

As we expected, this will not compile. Notably, our compiler might point us towards the following gaps:

  1. A declaration of anyName variable.
  2. A declaration of anotherUserWithTheSameName object.
  3. The variable areUsersEqual is both not declared and it does not hold the comparison result.
  4. If this is our first Statement, we might not even have the User class defined at all.

The compiler created a kind of a small TODO list for us, which is nice. Note that while we don't have full compiling code, filling the gaps boils down to making a few trivial declarations and assignments:

  1. anyName can be declared as var anyName = Any.String();
  2. anotherUserWithTheSameName can be declared as var anotherUserWithTheSameName = new User(anyName);
  3. areUsersEqual can be declared and assigned this way: var areUsersEqual = user.Equals(anotherUserWithTheSameName);
  4. If User class does not exist, we can add it by simply stating: public class User {}

Putting it all together:

[Fact] public void 
ShouldAppearEqualToAnotherUserWithTheSameName()
{
  //GIVEN
  var anyName = Any.String();
  var user = new User(anyName);
  var anotherUserWithTheSameName = new User(anyName);

  //WHEN
  var areUsersEqual = user.Equals(anotherUserWithTheSameName);

  //THEN
  Assert.True(areUsersEqual);
}

And that's it - the Statement is complete!

Sunday, 23 June 2013

How to start with a test, not implementation - part 1

(this post is adapted from my work-in-progress open source TDD tutorial)

Note that I use "Statement" instead of "test" and "Specification" instead of "Test Suite" in this post

Part 2 of this series is ready!

Whenever I sat down with a person that was about to write their first code in a Statement-first manner, the person would first stare at the screen, then at me, then would say: "what now?". It's easy to say: "You know how to write code, you know how to write a unit test for it, just this time start with the latter rather than the first", but for many people, this is something that blocks them completely. If you're one of them, don't worry - you're not alone. I decided to dedicate this series of posts solely to techniques for starting to write a Statement when there's no code. I do not use mocks in this series on purpose, to keep it simple. However, all of these techniques are proven to work when using mocks.

Start with a good name

It may sound obvious, but really some people are having trouble describing the behavior they expect from their code. If you can name such behavior, it's a great starting point.

I know some people don't pay attention to naming their Statements, mainly because they're considered as tests and second-level citizens - as long as they run and "prove the code does not contain defects", they're considered sufficient. We'll take a look at some examples of bad names and then I'd like to introduce to you some rules of good naming.

Consequences of bad naming

As I said, many people don't really care how their Statements are named. This is a symptom of treating the Specification as garbage or leftovers - something that just "runs through your code". Such situation is dangerous, because as soon as this kind of thinking is established, it leads to bad, unmaintainable Specification that looks more like lumps of accidental code put together in a haste than a living documentation. Imagine that your Specification consists of names like this:

  • TrySendPacket()
  • TrySendPacket2()
  • testSendingManyPackets()
  • testWrongPacketOrder1()
  • testWrongPacketOrder2()

and try for yourself how difficult it is to answer the following questions:

  1. How do you know what situation each Statement describes?
  2. How do you know whether the Statement describes a single situation, or few of them at the same time?
  3. How do you know whether the assertions inside those Statements are really the right ones assuming each Statement was written by someone else or was written a long time ago?
  4. How do you know whether the Statement should stay or be removed when you modify the functionality it specifies?
  5. If your changes in production code make a Statement evaluate to false, how do you know whether the Statement is no longer correct or the production code is wrong?
  6. How do you know whether you will not introduce a duplicate Statement for a behavior that's already specified by another Statement when adding to Specification originally created by another team member?
  7. How do you estimate, by looking at the runner tool report, whether the fix for failing Statement will be easy or not?
  8. How do you answer a new developer in your team when they ask you "what is this Statement for?"
  9. How can you keep track of the Statements already made about the specified class vs those that still need to be made?

What's in a good name?

For the name of the Statement to be of any use, it has to describe the expected behavior. At minimum, it should describe what happens at what circumstances. Let's take a look at one of the names Steve freeman and Nat Pryce came up in their great book Growing Object Oriented Software Guided By Tests:

notifiesListenersThatServerIsUnavailableWhenCannotConnectToItsMonitoringPort()

Note few things about the name of the Statement:

  1. It describes a behavior of an instance of a specified class. Note that it does not contain method name, because what is specified is not a method, but a behavior that has its entry point and expected result. The name simply tells that what an instance does (notifies listeners that server is unavailable) under certain circumstances (when cannot connect to its monitoring port). It's important because such description is what you can derive from thinking about responsibilities of a class, so you don't need to know any of its methods signatures or the code that's inside of the class. Hence, this is something you can come up without before implementing - you just need to know why you created this class and feed on it.
  2. The name is long. Really, really, really don't worry about it. As long as you're describing a single behavior, it's alright. I know usually people are hesitant to give long names to the Statements, because they try to apply the same rules to those names as method names in production code. Let me make it clear - these two cases are different. In case of Statements, they're not invoked by anyone besides the automatic runner applications, so they won't obfuscate any code that would need to call them. Sure, we could put all the information in the comment instead of Statement name and leave the name short, like this:

    [Fact]
    //Notifies listeners that server 
    //is unavailable when cannot connect
    //to its monitoring port
    public void Statement_002()
    {
     //...
    }
    

    There are two downsides of this solution: one is that we now have to put extra information (Statement_002) which is required only by compiler, because every method needs to have a name - there's usually no value a human could derive from such a name. The second downside is that when the Statement is evaluated to false, the automated runner shows you the following line: Statement_002: FAILED - note that all the information included in the comment isn't present in the failure report. It's really better to receive a report such as: notifiesListenersThatServerIsUnavailableWhenCannotConnectToItsMonitoringPort: FAILED, because all the information about the Statement that fails is present in the runner window.

  3. Using a name that describes a (single) behavior allows you to track quickly why the Statement is false when it is. Suppose a Statement is true when you start refactoring, but in the meantime it turns out to be false and the report in the runner looks like this: TrySendingHttpRequest: FAILED - it doesn't really tell you anything more than an attempt is made to send a HTTP request, but, for instance, does not tell you whether your specified object is the sender (that should try to send this request under some circumstances) or the receiver (that should handle such request properly). To actually know what went wrong, you have to go to the code to scan its source code. Now compare it to the following name: ShouldRespondWithAnAckWheneverItReceivedAHttpRequest. Now when it evaluates to false, you can tell that what is broken is that the object no longer responds with an ACK to HTTP request. Sometimes this is enough to deduct which part of the code is in fault of this evaluation failure.

My favourite convention

There are many conventions for naming Statements appropriately. My favorite is the one developed by Dan North, which makes each Statement name begin with the word Should. So for example, I'd name a Statement: ShouldReportAllErrorsSortedAlphabeticallyWhenItEncountersErrorsDuringSearch(). The name of the Specification (i.e. class name) answers the question "who should do it?", i.e. when I have a class named SortingOperation and want to say that it "should sort all items in ascending order when performed", I say it like this:

public class SortingOperationSpecification
{
 [Fact] public void
 ShouldSortAllItemsInAscendingOrderWhenPerformed()
 {
 }
}

It's important to focus on what result is expected from an object when writing names along this convention. If you don't, you quickly find it troublesome. As an example, one of my colleague was specifying a class UserId and wrote the following name for the Statement about comparison of two identifiers: EqualOperationShouldPassForTwoInstancesWithTheSameUserName(). Note that this is not from the perspective of a single object, but rather from the perspective of an operation that's executed on it, which means that we stopped thinking in terms of object responsibilities and started thinking in terms of operation correctness, which is farther away from our assumption that we're writing a Specification consisting o Statements. This name should be changed to: ShouldReportThatItIsEqualToAnotherObjectWhenItHasTheSameUserName().

And this is the end of part 1 of the series. In the next installment, we'll take a look at another technique - brute force translation of prose description written with GIVEN-WHEN-THEN structure to code.

Monday, 10 June 2013

An expert and a novice practicing TDD - story

(this post is adapted from my work-in-progress open source TDD tutorial)

And now, a taste of things to come!

- Shang Tsung, Mortal Kombat The Movie

The above quote took place just before a fighting scene in which a nameless warrior jumped at Sub-Zero only to be frozen and broken into multiple pieces upon hitting the wall. The scene was not spectacular in terms of fighting technique or length. Also, the nameless guy didn't even try hard - the only thing he did was to jump only to be hit by a freezing ball, which, by the way, he actually saw coming. It looked a lot like the fight was set up only to showcase Sub-Zero's freezing ability. Guess what? In this post, we're gonna do roughly the same thing - set up a fake, easy scenario just to showcase some of the basic TDD elements!

As we go through the example, you might wonder how on earth could you possibly write real applications the way we'll write our simple program. Don't worry, I'm just not showing you all the tricks to spare you confusion and deliver something digestive instead of bloat of information that could kill you. In other words, the example will be as close to real world problems as the fight between Sub-Zero and nameless ninja was to real martial arts fight, but will show you some of the elements of TDD process.

Let me tell you a story

Meet Johnny and Benjamin, two developers from Buthig Company. Johnny is quite fluent in programming and Test-Driven Development, while Benjamin is an intern under Johnny's mentorship and is eager to learn TDD. They're on their way to their customer, Jane, who requested their presence as she wants them to do write a small program for her. Together with them, we'll see how they interact with the customer and how Benjamin tries to understand the basics of TDD. Just as you, Benjamin is a novice so his questions may reflect yours.

Act 1: The Car

Johnny: How do you feel on your first assignment?

Benjamin: I'm pretty excited! I hope I can learn some of the TDD stuff you promised to teach me.

Johnny: Not only TDD, but we're also gonna use some of the practices associated with a process called Acceptance Test Driven Development, albeit in a simplified form.

Benjamin: Acceptance Test-Driven Development? What is that?

Johnny: While TDD is usually referred to as a development technique, ATDD is something more of a collaboration method. Both ATDD and TDD have a bit of analysis in them and work very well together as both use the same underlying principles, just on different levels. We'll need only a small subset of what ATDD has to offer, so don't get over-excited.

Benjamin: Sure.

Act 2: The Customer

Johnny: Hi, Jane, how are you?

Jane: Thanks, I'm fine, how about you?

Johnny: Same here, thanks. So, can you tell us a bit about the software you need us to write?

Jane: Sure. Recently, I bought a new smartphone as a replacement for my old one. The thing is, I'm really used to the calculator application that was running on the previous phone and I can't find it for my current device.

Benjamin: Can't you just use another calculator app? There are plenty of them available to download from the web.

Jane: That's right. I checked them all and none has exactly the same behavior as the one I was using for my tax calculations. You know, the program was like right hand to me and it had some really nice shortcuts that made my life easier.

Johnny: So you want us to reproduce the application to run on your new device?

Jane: Yes.

Johnny: Are you aware that apart from the fancy features that you were using we'll have to allocate some effort to implement the basics that all the calculators have?

Jane: Sure, I'm OK with that. I'm so used to my calculator application so much that if I use something else for more than few months, I'll have to pay psychotherapist instead of you guys. Apart from that, writing a calculator app seems like a simple task in my mind, so the cost isn't going to be overwhelming, right?

Johnny: I think I get it. Let's get it going then. We'll be implementing the functionality incrementally, starting with the most essential ones. Which feature of the calculator would you consider the most essential?

Jane: That would be addition of numbers, I guess.

Johnny: Ok, that will be our target for the first iteration. After the iteration, we'll deliver this part of functionality for you to try out and give us some feedback. However, before we can even implement addition, we'll have to implement displaying digits on the screen as you enter them. Is that correct?

Jane: yes, I want the display stuff to work as well - it's the most basic feature, so...

Johnny: Ok then, this is a simple functionality, so let me suggest some user stories as I understand what you already said and you'll correct me where I'm wrong. Here we go:

  1. In order to know that the calculator is turned on, As a tax payer I want to see "0" on the screen as soon as I turn it on.
  2. In order to see what numbers I'm currently operating on, As a tax payer, I want the calculator to display the values I enter
  3. In order to calculate sum of my different incomes, As a tax payer I want the calculator to enable addition of multiple numbers

What do you think?

Jane: The stories pretty much reflect what I want for the first iteration. I don't think I have any corrections to make.

Johnny: Now we'll take each story and collect some examples of how it should work.

Benjamin: Johnny, don't you think it's obvious enough to proceed with implementation straight away?

Johnny: Trust me, Benjamin, if there's one word I fear most in communication, it's "obvious". Miscommunication happens most often around things that people consider obvious, simply because other people don't.

Jane: Ok, I'm in. What do I do?

Johnny: Let's go through stories one by one and see if we can find some key examples of how the features work. The first story is...

In order to know that the calculator is turned on, As a tax payer I want to see "0" on the screen as soon as I turn it on.

Jane: I don't think there's much to talk about. If you display "0", I'll be happy. That's all.

Johnny: Let's write down this example:

key sequence Displayed output Notes
N/A 0 Initial displayed value

Benjamin: That makes me wonder... what should happen when I press "0" again at this stage?

Johnny: Good catch, that's what these examples are for - they make our thinking concrete. As Ken Pugh says: "Often the complete understanding of a concept does not occur until someone tries to use the concept". We'd normally put it on a TODO list, because it's part of a different story, but we're actually done with this one, so let's move straight to the story about displaying entered digits. How about it, Jane?

Jane: Agree.

In order to see what numbers I'm currently operating on, As a tax payer, I want the calculator to display the values I enter

Johnny: Let's begin with the case raised by Benjamin. What should happen when I input "0" multiple times after I have only "0" on the display?

Jane: Just one "0" should be displayed

Johnny: Do you mean this?

key sequence Displayed output Notes
0,0,0 0 Zero is a special case – it is displayed only once

Jane: That's right. Other than this, the digits should just show on the screen, like this:

key sequence Displayed output Notes
1,2,3 123 Entered digits are displayed

Benjamin: How about this:

key sequence Displayed output Notes
1,2,3,4,5,6,7,1,2,3,4,5,6 1234567123456? Entered digits are displayed?

Jane: Actually, no. My old calculator app has a limit of six digits that I can enter, so it should be:

key sequence Displayed output Notes
1,2,3,4,5,6,7,1,2,3,4,5,6 123456 Display limited to six digits

Johnny: another good catch, Benjamin!

Benjamin: I think I'm beginning to understand why you like working with examples!

Johnny: Good. Is there anything else, Jane?

Jane: No, that's pretty much it. Let's take another story.

In order to calculate sum of my different incomes, As a tax payer I want the calculator to enable addition of multiple numbers

Johnny: Is the following all we have to support?

key sequence Displayed output Notes
2,+,3,+,4,= 9 Simple addition of numbers

Jane: This scenario is correct, however, there's also a case when I start with "+" without inputting any number before. This should be treated as adding to zero:

key sequence Displayed output Notes
+,1,= 1 Addition shortcut – treated as 0+1

Benjamin: How about when the output is a number longer than six digits limit? Is it OK that we truncate it like this?

key sequence Displayed output Notes
9,9,9,9,9,9,+,9,9,9,9,9,9,= 199999 Our display is limited to six digits only

Jane: Sure, I don't mind. I don't add such big numbers anyway.

Johnny: There's still one question we missed. Let's say that I input a number, then press "+" and then another number without asking for result with "=". What should I see?

Jane: Every time you press "+", the calculator should consider entering current number finished and overwrite it as soon as you press any other digit:

key sequence Displayed output Notes
2,+,3 3 Digits entered after + operator are treated as digits of a new number, the previous one is stored

Jane: Oh, and just asking for result after the calculator is turned on should result in "0".

key sequence Displayed output Notes
= 0 Result key in itself does nothing

Johnny: Let's sum up our discoveries:

key sequence Displayed output Notes
N/A 0 Initial displayed value
1,2,3 123 Entered digits are displayed
0,0,0 0 Zero is a special case – it is displayed only once
1,2,3,4,5,6,7 123456 Our display is limited to six digits only
2,+,3 3 Digits entered after + operator are treated as digits of a new number, the previous one is stored
= 0 Result key in itself does nothing
+,1,= 1 Addition shortcut – treated as 0+1
2,+,3,+,4,= 9 Simple addition of numbers
9,9,9,9,9,9,+,9,9,9,9,9,9,= 199999 Our display is limited to six digits only

Johnny: The limiting of digits displayed looks like a whole new feature, so I suggest we add it to the backlog and do it in another sprint. In this sprint, we won't handle such situation at all. How about that, Jane?

Jane: Fine with me. Looks like a lot of work. Nice that we discovered it up-front. For me, the limiting capability seemed so obvious that I didn't even think it would be worth mentioning.

Johnny: See? That's why I don't like the word "obvious". Jane, we'll get back to you if any more questions arise. For now, II think we know enough to implement these three stories for you.

Jane: good luck!

Act 3: Test-Driven Development

Benjamin: Wow, that was cool. Was that Acceptance Test-Driven Development?

Johnny: In a greatly simplified version, yes. The reason I took you with me was to show you the similarities between working with customer the way we did and working with the code using TDD process. They're both applying the same set of principles, just on different levels.

Benjamin: I'm dying to see it with my own eyes. Shall we start?

Johnny: Sure. If we followed the ATDD process, we'd start writing what we call acceptance-level specification. In our case, however, a unit-level specification will be enough. Let's take the first example:

Statement 1: Calculator should display 0 on creation

key sequence Displayed output Notes
N/A 0 Initial displayed value

Johnny: Benjamin, try to write the first Statement.

Benjamin: Boy, I don't know how to start.

Johnny: start by writing the statement in a plain English. What should the calculator do?

Benjamin: It should display "0" when I turn the application on.

Johnny: In our case, "turning on" is creating a calculator. Let's write it down as a method name:

public class CalculatorSpecification
{

[Fact] public void
ShouldDisplay0WhenCreated()
{

}

}

Benjamin: Why is the name of the class CalculatorSpecification and the name of the method ShouldDisplay0WhenCreated?

Johnny: It's a naming convention. There are many others, but this is the one that I like. The rule is that when you take the name of the class without the Specification part followed by the name of the method, it should form a legit sentence. For instance, if I apply it to what we wrote, it would make a sentence: "Calculator should display 0 when created".

Benjamin: Ah, I see now. So it's a statement of behavior, isn't it?

Johnny: That's right. Now, the second trick I can sell to you is that if you don't know what code to start with, start with the expected result. In our case, we're expecting that the behavior will end up as displaying "0", right? So let's just write it in form of an assertion.

Benjamin: You mean something like this?

public class CalculatorSpecification
{

[Fact] public void
ShouldDisplay0WhenCreated()
{
 Assert.Equal("0", displayedResult);
}

}

Johnny: Precisely.

Benjamin: But that doesn't even compile. What use is it?

Johnny: the code not compiling is the feedback that you needed to proceed. While previously you didn't know where to start, now you have a clear goal - make this code compile. Firstly, whre do you get the displayed value?

Benjamin: From the calculator display, of course!

Johnny: Then write it down how you get the value form the display.

Benjamin: like how?

Johnny: like this:

public class CalculatorSpecification
{

[Fact] public void
ShouldDisplay0WhenCreated()
{
 var displayedResult = calculator.Display();

 Assert.Equal("0", displayedResult);
}

}

Benjamin: I see. Now the calculator is not created anywhere. I need to create it somewhere now or it won't compile and this is my next step. Is this how it works?

Johnny: Yes, you're catching on quickly.

Benjamin: Ok then, here goes:

public class CalculatorSpecification
{

[Fact] public void
ShouldDisplay0WhenCreated()
{
 var calculator = new Calculator();

 var displayedResult = calculator.Display();

 Assert.Equal("0", displayedResult);
}

}

Johnny: Bravo!

Benjamin: Now the code still does not compile, because I don't have the Calculator class at all...

Johnny: sounds like a good reason to create it.

Benjamin: OK.

public class Calculator
{
}

Benjamin: Looks like the Display() method is missing too. I'll add it.

public class Calculator
{
  public string Display()
  {
    return "0";
  } 
}

Johnny: Hey hey, not so fast!

Benjamin: What?

Johnny: You already provided an implementation that will make our current Statement true. Remember its name? ShouldDisplay0WhenCreated - and that's exactly what the code you wrote does. Before we arrive at this point, let's make sure this Statement can ever be evaluates as false. So for now, let's change it to this:

public class Calculator
{
  public string Display()
  {
    return "Once upon a time in Africa...";
  } 
}

Johnny: Look, now we can run the Specification and watch that Statement evaluate to false, because it expects "0", but gets "Once upon a time in Africa...".

Benjamin: Running... Ok, it's false. By the way, do you always use such silly values to make Statements false?

Johnny: Hahaha, no, I just did it to emphasize the point. Normally, I'd write return ""; or something similarly simple. Now we can evaluate the Statement and see it evaluate to false. Now we are sure that we have not yet implemented what is required for the Statement to be true.

Benjamin: I think I get it. For now, the Statement shows that we don't have something we need and gives us a reason to add this "thing". When we do so, this Statement will show that we do have what we need. So what do we do now?

Johnny: Write the simplest thing that makes this Statement true.

Benjamin: like this?

public class Calculator
{
  public string Display()
  {
    return "0";
  } 
}

Johnny: Yes.

Benjamin: But that's not a real implementation. What's the value behind putting in a hardcoded string? The final implementation is not going to be like this for sure!

Johnny: You're right. The final implementation is most probably going to be different. What we did, however, is still valuable because:

  1. You're one step closer to implementing the final solution
  2. This feeling that this is not the final implementation points you towards writing more Statements. When there is enough Statements to make your implementation complete, it usually means that you have a complete Specification of class behaviors as well.
  3. If you treat making every Statement true as an achievement, this practice allows you to evolve your code without losing what you already achieved. If by accident you break any of the behaviors you've already implemented, the Specification is going to tell you because one of the existing Statements that were previously true will then evaluate to false. You can then either fix it or undo your changes using version control and start over from the point where all existing Statements were true.

Benjamin: looks like quite a few benefits. Still, I'll have to get used to this kind of working.

Johnny: don't worry, it is central to TDD, so you'll grasp it in no time. Now, before we proceed to the next Statement, let's look at what we already achieved. First, we wrote a Statement that turned out false. Then, we wrote just enough code to make the Statement true. Time for a step called Refactoring. In this step, we'll take a look at the Statement and the code and remove duplication. Can you see what's duplicated between the Statement and the code?

Benjamin: both of them contain the literal "0". The Statement has it here:

Assert.Equal("0", displayedResult);

and the implementation here:

return "0";

Johnny: Good, let's eliminate this duplication by introducing a constant called InitialValue. The Statement will now look like this:

[Fact] public void
ShouldDisplayInitialValueWhenCreated()
{
 var calculator = new Calculator();

 var displayedResult = calculator.Display();

 Assert.Equal(Calculator.InitialValue, displayedResult);
}

and the implementation:

public class Calculator
{
  public const string InitialValue = "0";
  public string Display()
  {
    return InitialValue;
  } 
}

Benjamin: The code looks better and having the constant in one place will make it more maintainable, but I think the Statement in its current form is weaker than before. We could change the InitialValue to anything and the Statement would still be true, since it does not force this constant to be "0".

That's right. We need to add it to our TODO list to handle this case. Can you write it down?

Benjamin: Sure. I'll write it as "TODO: 0 should be used as an initial value."

Johnny: Ok. We should handle it now, especially that it's part of the story we're currently implementing, but I'll leave it for later just to show you the power of TODO list in TDD - whatever is on the list, we can forget and get back to when we have nothing better to do. Our next item from the list is this:

Statement 2: Calculator should display entered digits

key sequence Displayed output Notes
1,2,3 123 Entered digits are displayed

Johnny: Benjamin, can you come up with a Statement for this behavior?

Benjamin: I'll try. Here goes:

public void [Fact]
ShouldDisplayEnteredDigits()
{
  var calculator = new Calculator();
  
  calculator.Enter(1);
  calculator.Enter(2);
  calculator.Enter(3);
  var displayedValue = calculator.Display();

  Assert.Equal("123", displayedValue);
}

Johnny: I'm glad that you got the part about naming and writing a Statement. One thing we'll have to work on here though.

Benjamin: what is it?

Johnny: When we talked to Jane, we used examples with real values. These real values were extremely helpful in pinning down the corner cases and uncovering missing scenarios. They were easier to imagine as well, so they were a perfect suit for conversation. If we were automating these examples on acceptance level, we'd use those real values as well. When we write unit-level Statements, however, we use a different technique to get this kind of specification more abstract. First of all, let me enumerate the weaknesses of the approach you just used:

  1. Making a method Enter() accept an integer value suggests that one can enter more than one digit at once, e.g. calculator.Enter(123), which is not what we want. We could detect such cases and throw exceptions if the value is outside the 0-9 range, but there are better ways when we know we'll only be supporting ten digits (0,1,2,3,4,5,6,7,8,9).
  2. The Statement does not clearly show the relationship between input and output. Of course, in this simple case it's pretty self-evident that the sum is a concatenation of entered digits, we don't want anyone who will be reading this Specification in the future to have to guess.
  3. The Statement suggests that what you wrote is sufficient for any value, which isn't true, since the behavior for "0" is different (no matter how many times we enter "0", the result is just "0")

Hence, I propose the following:

public void [Fact]
ShouldDisplayAllEnteredDigitsThatAreNotLeadingZeroes()
{
 //GIVEN
 var calculator = new Calculator();
 var nonZeroDigit = Any.Besides(DigitKeys.Zero);
 var anyDigit1 = Any.Of<DigitKeys>();
 var anyDigit2 = Any.Of<DigitKeys>();
          
 //WHEN
 calculator.Enter(nonZeroDigit);
 calculator.Enter(anyDigit1);
 calculator.Enter(anyDigit2);
          
 //THEN
 Assert.Equal(
  string.Format("{0}{1}{2}", 
   (int)nonZeroDigit, 
   (int)anyDigit1, 
   (int)anyDigit2
  ),
  calculator.Display()
 );
}

Benjamin: Johnny, I'm lost! Can you explain what's going on here?

Johnny: Sure, what do you want to know?

Benjamin: For instance, what is this DigitKeys type doing here?

Johnny: It's supposed to be an enumeration (note that it does not exist yet, we just assume that we have it) to hold all the possible digits user can enter, which are 0-9. This is to ensure that user will not write calculator.Enter(123). Instead of allowing to enter any number and then detecting errors, we're giving our users a choice from among only the valid values.

Benjamin: Now I get it. So how about the Any.Besides() and Any.Of()? What do they do?

Johnny: They're methods from a small utility library I'm using when writing unit-level Specifications. Any.Besides() returns any value from enumeration besides the one supplied as an argument. Hence, the call Any.Besides(DigitKeys.Zero) means "any of the values contained in DigitKeys enumeration, but not DigitKeys.Zero".

The Any.Of() is simpler - it just returns any value in an enumeration. Note that when I create the values this way, I state explicitly that this behavior occurs when first digit is non-zero. This technique of using generated values instead of literals has its own principles, but let's leave it for later. I promise to give you a detailed lecture on it. Agree?

Benjamin: You better do, because for now, I feel a bit uneasy with generating the values - it seems like the Statement we're writing is getting less deterministic this way. The last question - what about those weird comments you put in the code? Given? When? Then?

Johnny: Yes, this is a convention that I use, not only in writing, but in thinking as well. I like to think about every behavior in terms of three elements: assumptions (given), trigger (when) and expected result (then). Using the words, we can summarize the Statement we're writing in the following way: "Given a calculator, when I enter some digits first one being other than zero, then they should all be displayed in order they were entered". This is also something that I'll tell you more about later.

Benjamin: Sure, for now I need just enough detail to understand what's going on - we can talk about the principles, pros and cons later. By the way, the following sequence of casts looks a little bit ugly:

  string.Format("{0}{1}{2}", 
   (int)nonZeroDigit, 
   (int)anyDigit1, 
   (int)anyDigit2
  )

Johnny: We'll get back to it and make it more "smarter" in a second after we make this statement true. For now, we need something obvious. Something we know works. Let's evaluate this Statement. What's the result?

Benjamin: Failed, expected "331", but was "0".

Johnny: Good, now let's write some code to make this Statement true. First, let's introduce an enumeration of digits:

public enum DigitKeys
{
 Zero = 0,
 TODO1, //TODO
 TODO2, //TODO
 TODO3, //TODO
 TODO4, //TODO
}

Benjamin: What's with all those bogus values? Shouldn't we just enter all the digits we support?

Johnny: Nope, not yet. We still don't have a Statement which would say what digits are supported and thus make us add them, right?.

Benjamin: You say you need a Statement for an element to be in an enum??

Johnny: This is a specification we're writing, remember? It should say somewhere which digits we support, shouldn't it?

Benjamin: It's difficult to agree with, especially that I'm used to write unit tests, not Statements and in unit tests, I wanted to verify what I was unsure of.

Johnny: I'll try to give you more arguments later. For now, just bear with me and note that adding such Statement will be almost effortless.

Benjamin: OK.

Johnny: Now for the implementation. Just to remind you - up to now, it looked like this:

public class Calculator
{
  public const string InitialValue = "0";
  public string Display()
  {
    return InitialValue;
  } 
}

This is not enough to support displaying multiple digits (as we saw, because the Statement saying they should be supported was evaluated to false). So let's evolve the code to handle this case:

public class Calculator
{
 private int _result = 0;
      
 public void Enter(DigitKeys digit)
 {
  _result *= 10;
  _result += (int)digit; 
 }

 public string Display()
 {
  return _result.ToString();
 }
}

Johnny: Now the Statement is true so we can go back to it and make it a little bit prettier. Let's take a second look at it:

public void [Fact]
ShouldDisplayAllEnteredDigitsThatAreNotLeadingZeroes()
{
 //GIVEN
 var calculator = new Calculator();
 var nonZeroDigit = Any.Besides(DigitKeys.Zero);
 var anyDigit1 = Any.Of<DigitKeys>();
 var anyDigit2 = Any.Of<DigitKeys>();
          
 //WHEN
 calculator.Enter(nonZeroDigit);
 calculator.Enter(anyDigit1);
 calculator.Enter(anyDigit2);
          
 //THEN
 Assert.Equal(
  string.Format("{0}{1}{2}", 
   (int)nonZeroDigit, 
   (int)anyDigit1, 
   (int)anyDigit2
  ),
  calculator.Display()
 );
}

Johnny: Remember you said that you don't like the part where string.Format() is used?

Benjamin: Yeah, it seems a bit unreadable.

Johnny: Let's extract this part into a utility method and make it more general - we'll need a way of constructing expected displayed output in many of our future Statements. Here's my go at this helper method:

string StringConsistingOf(params DigitKeys[] digits)
{
 var result = string.Empty;
      
 foreach(var digit in digits) 
 {
  result += (int)digit;
 }
 return result;
}

Note that this is more general as it supports any number of parameters. And the Statement after this extraction looks like this:

public void [Fact]
ShouldDisplayAllEnteredDigitsThatAreNotLeadingZeroes()
{
 //GIVEN
 var calculator = new Calculator();
 var nonZeroDigit = Any.Besides(DigitKeys.Zero);
 var anyDigit1 = Any.Of<DigitKeys>();
 var anyDigit2 = Any.Of<DigitKeys>();
          
 //WHEN
 calculator.Enter(nonZeroDigit);
 calculator.Enter(anyDigit1);
 calculator.Enter(anyDigit2);
          
 //THEN
 Assert.AreEqual(
  StringConsistingOf(nonZeroDigit, anyDigit1, anyDigit2),
  calculator.Display()
 );
}

Benjamin: Looks better to me. The Statement is still evaluated as true, which means we got it right, didn't we?

Johnny: Not exactly. With such moves such as this one, I like to be extra careful. Let's comment out the body of the Enter() method and see if this Statement can still be made false by the implementation:

public void Enter(DigitKeys digit)
 {
  //_result *= 10;
  //_result += (int)digit; 
 }

Benjamin: Running... Ok, it is false now. Expected "243", got "0".

Johnny: good, now we're pretty sure that it works OK. Let's uncomment the lines we just commented out and move forward.

Statement 3: Calculator should display only one zero digit if it's the only entered digit even if it is entered multiple times

Johnny: Benjamin, this should be easy for you, so go ahead and try it. It's really a variation of previous Statement.

Benjamin: Let me try... ok, here it is:

[Fact] public void 
ShouldDisplayOnlyOneZeroDigitWhenItIsTheOnlyEnteredDigitEvenIfItIsEnteredMultipleTimes()
{
 var zero = DigitKeys.Zero;
 var calculator = new Calculator();
      
 calculator.Enter(zero);
 calculator.Enter(zero);      
 calculator.Enter(zero);
      
 Assert.Equal(
  StringConsistingOf(DigitKeys.Zero), 
  calculator.Display()
 );
}

Johnny: Good, you're learning fast! Let's evaluate this Statement.

Benjamin: It seems that our current code already fulfills the Statement. Should I try to comment some code to make sure this Statement can fail just like you did in the previous Statement?

Johnny: That would be wise thing to do. When a Statement is true without requiring you to change any production code, it's always suspicious. Just like you said, we have to modify production code for a second to force this Statement to be false, then undo this modification to make it true again. This isn't as obvious as previously, so let me do it. I'll mark all the added lines with //+ comment so that you can see them easily:

public class Calculator
{
 int _result = 0;
 string fakeResult = "0"; //+
      
 public void Enter(DigitKeys digit)
 {
  _result *= 10;
  _result += (int)digit; 
  if(digit == DigitKeys.Zero) //+
  {  //+
    fakeResult += "0";  //+
  } //+
 }

 public string Display()
 {
  if(_result == 0)  //+
  {  //+
   return fakeResult;  //+
  }  //+
  return _result.ToString();
 }
}

Benjamin: Wow, looks like a lot of code just to make the Statement false! Is it worth the hassle? We'll undo this whole modification in a second anyway

Johnny: Depends on how confident you want to feel. I'd say that it's usually worth it - at least you know that you got everything right. It might seem like a lot of work, but it actually took me about a minute to add this code and imagine you got it wrong and had to debug it on a production environment. _That_ would be a waste of time.

Benjamin: Ok, I think I get it. Since we saw this Statement turn false, I'll undo this modification to make it true again.

Johnny: Sure.

Epilogue

Time to leave Johnny and Benjamin, at least for now. I actually planned to make this post longer, and cover all the other operations, but I fear I'd make this too long and get you bored. You should have a feel of how the TDD cycle looks like, especially that Johnny and Benjamin had a lot of conversations on many other topics in the meantime. some of the topics are already expanded on this blog, some others I'll be touching sometime soon.

Till next time!

Tuesday, 7 May 2013

When a 'test' becomes something else

(this post is adapted from my work-in-progress open source TDD tutorial)

Everyday life example

I studied in Łódź, a large city in the center of Poland. As probably all other students in all other countries, we've had lectures, exercises and exams. The exams were pretty hard, especially considered that my computer science group was on the faculty of electronic and electric engineering, so we had to grasp a lot of classes that didn't have anything to do with programming, for instance electrotechnics, solid-state physics or electronic and electrical metrology.

Knowing that the exams were difficult and that it was hard to learn everything during preparation, the lecturers would give us exemplary exams from previous years. The questions were different than during actual exams, but the structure and types of questions asked (practice vs. theory etc.) was similar. We would usually get these exemplary questions before we started learning really hard (which was usually at the end of semester). Guess what happened then? As you might suspect, we did not use the tests we received just to 'verify' or 'check' our knowledge after we finished learning. Those tests were actually the first thing we went to before even starting to learn. Why was that so? What use were the tests when we knew we wouldn't know most of the answers?

I guess my lecturers would disagree with me, but I find it quite amusing that what we were really doing back then was 'Lean'. Lean is an approach where, among others, there's a rigorous emphasis on eliminating waste. Every feature or product that's produced while not being needed by anyone is considered waste. That's because if something's not needed, there's no reason to assume it will ever be needed. In such case the entire feature or product is a waste - it has no value. Even if it WILL be ever needed, it will require some rework anyway to fit the real customer needs. In this case, the initial amount of work that went into the parts of solution that had to be replaced by another parts anyway is a waste - it never brought any money (I'm not talking about such things as customer demos - their value lies somewhere else).

So, in order to eliminate waste, there's huge pressure nowadays to "pull features from demand" instead of "pushing them" into the product "for future". In other words, every feature is there to satisfy concrete need. If not, the effort is considered wasted and the money drown.

Going back to the exams, why the approach of first looking through the exemplary tests can be considered 'lean'? That's because, when we treat passing an exam as our goal, then everything that does not put us closer to this goal is considered a waste. Let's suppose the exam is theory only - why then practice the exercises? It would probably pay off a lot more to study theoretical side of the topics. Such knowledge could be obtained from those exemplary tests. So, the tests were a kind of specification of what was needed to pass the exam, letting us pull the value (i.e. our knowledge) from demand (information obtained from a realistic tests) rather that pushing it from implementation (i.e. learning everything in a course book chapter after chapter).

So the tests became something else. They proved very valuable before the 'implementation' (i.e. learning for the exam) because:

  1. they helped us focus on what was needed to reach our goal
  2. they brought our attention away from what was not needed to reach our goal

That was the value of a test before learning. Note that the tests we would usually receive were not exactly what we'd encounter at the time of exam, so we still had to guess. Still, the role of a test as specification of a need was already visible.

Taking It To The Software Development Land

I chose this lengthy metaphor to show you that 'test' is really another way of specifying a requirement or a need and that it's not something that's counter-intuitive - it occurs in our everyday lives. This is also true in software development. Let's take the following 'test' and see what kind of needs it specifies:

var reporting = new ReportingFeature();
var anyPowerUser = Any.Of(Users.Admin, Users.Auditor);
Assert.True(reporting.CanBePerformedBy(anyPowerUser));

(In this example, we used Any.Of() method that returns any enumeration value from the specified list. Here, we say "give me a value that's either Users.Admin or Users.Auditor".)

Let's look at those (only!) three lines of code, imagining that the production code that makes this 'test' pass does not exist yet. What can we learn from these three lines about what the code needs to supply? Count with me:

  1. We need a reporting feature
  2. We need to support a notion of users and privileges
  3. We need to support a domain concept of power user, who is either an administrator or an auditor
  4. Power users will need to be privileged to use the reporting feature (note that it does not specify which other users should or should not be able to use this feature - we'd need a separate 'test' for that).

Also, we are already after the phase of designing an API that will fulfill the need. Don't you think it's pretty much information about the application from just three lines of code?

A Specification Instead of a Test Suite

I hope that you can see now that what we called 'a test' is really a kind of specification. This "discovery" is quite recent, so there isn't a clear terminology on it yet. Some like to call the process of using tests as specifications: Specification By Example, to say that the tests are really examples that help specify and clarify the behavior of developed part of functionality. The terminology is still not rock-solid, so you might encounter different naming for different things. For example, a 'test' is often referred to as 'spec', or an 'example', or a 'behavior description', or a 'specification statement' or a 'fact about the developed system' (the xUnit.NET framework marks each 'test' with a [Fact] attribute, suggesting that by writing it, we're stating a single fact about developed code. By the way, xUnit.NET also allows us to state 'theories' about our code, but let's leave it for now).

From your experience, you may know paper or word specifications, written in plain English or other spoken language. Our specification is different than these specifications in at least few ways:

  1. it's written one statement at a time.
  2. it's executable - you can run it to see whether the code adheres to the specification or not.
  3. it's written in source code rather than in spoken language - which is both good (less room for misunderstanding - source code is the most formal and structured way of writing specifications) and bad (great care must be taken to keep such specification readable).

Friday, 12 April 2013

Motivation - the first step to learning TDD

So, you want to learn TDD, huh? First off, let me ask a question 'why?'.

I'll tell you a story - few years ago, I had an apprentice, who wanted to learn TDD as well. We started working on a small project together for him to grasp the necessary skills through practice, with me sitting next to him, providing guidance. He showed up three, four times, then he resigned, having 'more urgent things' and 'no time'. Since then, he did not progress in TDD at all. Did he really want to learn?

See, sometimes people don't really want to learn something or don't want so much as to dedicate the necessary time and resources to this. Sometimes they just think they need to do it for whatever reasons - getting promotion at work, gaining a certificate, add something to CV or just 'stay up to date' with recent hypes, one of them being Test-Driven Development.

Others want to learn TDD for reasons they themselves made up. Knowing that TDD is valued and praised by others, they draw conclusions that it has to be good. Why is that so? Err... maybe... "the code will be more tested"? Or... ekhem... "the network of tests will be tighter"? And why do Test-First? Well... hmm... "to ensure tests are written for everything"? While these statements might be partially true, they don't even touch the essence of the "why" of TDD, and quickly turn into something like: "I don't really need TDD, because I need tests that give me confidence on broader scope"(*) or: "Why do I need unit tests when I already have integration tests, smoke tests, sanity tests, exploration tests...?". Then TDD gets abandoned, before actually ever being achieved in the first place.

You need to be highly motivated on this one. If you're not, hey, I heard the new series of Game Of Thrones is on the TV, why don't you check it out instead? Ok, I'm just teasing, however, as some say, TDD is "easy to learn, hard to master" (don't actually know who said it first, I searched the web and found it in few places where none of the writers gave credit to anyone else for it, so I decided just to mention that I'm not the one that coined this description) , so without some guts to move on, it will be hard.

How TDD feels like

Me and my brother liked to play video games in our childhood - one of the most memorable being Tekken 3 - a japanese tournament beat'em up for Sony Playstation. To finish the game with all warriors, unlock all hidden characters, mini-games etc. took about a day. Some could say the game had nothing to offer since then. Why then did we spend over a year on it?

It's because each fighter in the game had a lot of combos, kicks and punches that could be mixed in a variety of ways. Some were only usable in some situations, others were something you could throw at your opponent almost anytime without a big risk of being exposed to counterattacks. You could side-step to evade enemy's attacks and, most of all, you could kick another fighter up in the air where they could not block your attacks and you were able to land some nice attacks on them before they fell down. These in-the-air techniques were called "juggles". There were magazines that published a list of new possible juggles each month and the hype stayed in the gaming community for well over a year.

Yes, Tekken was easy to learn - you could put one hour into training the core moves of a character and then be able to "use them", but what made you a great fighter was the experience and knowledge on which techniques were risky or not, which ones could be used in which situations, which ones, if used one after another, gave the opponent little chance to counterattack etc. No wonder that soon, many tournaments sprang, where players could clash for glory, fame and rewards. Even today, you can watch some of the matches on youtube.

TDD is like Tekken. You've probably heard the mantra "red-green-refactor" or the general advice "write your test first, then the code", maybe you even did some experiments on your own where you were trying to implement a bubble-sort algorithm or other simple stuff by starting with a test. But that's all like practicing Tekken by trying out each move on its own on dummy opponent, without the context of real-world issues that make the fight really challenging. And while I think such exercises are (somewhat) useful, you need to understand the bigger picture.

Some people I talk with about TDD sum up what I say to them as: "That's really demotivating - there are so many things I have to watch out for that it makes me never want to start!". Easy, don't panic - remember the first time you tried to ride a bike - you must've been really far back then from knowing traffic regulations and following road signs, but that didn't really keep you away, did it? I myself found TDD very exciting. Some guys my age already think they know all about coding, are bored with it and can't wait until they move to management or requirements or business analysis, but hey! I have a new technique that makes my coding career challenging again! And it's not something I can use only when I pay to Microsoft or Oracle or anybody else - it's quite portable, making me a better developer overall!

(*)To be more precise, TDD is not only about unit tests, but is viewed as such by many.

Wednesday, 27 March 2013

Implementation Readability vs Design Readability and what TDD has to do with it?

Many thanks to Łukasz Bigos for discussion and inspiration.

Recently I had a discussion with few of my colleagues on readability of Test-driven (or more precisely - loosely coupled) code. The thing that surprised me the most was the argument that loosely coupled code is unreadable. The surprise comes from my deep conviction that TDD help achieve extremely readable code.

In my mind, TDD leads to well factored, small classes with single responsibilities, but in some others' minds, it's just "obfuscating the picture" and they "don't know what's happening in this code because everything is hidden and split across many classes and methods".

So I thought about it some more, talked a little more and finally I reached a conclusion on why there's a disagreement between us.

Two ways of understanding readability

It appears that, depending on our habits, "readable" means something different to us. There are two kinds of readability I learned to differentiate: implementation readability and design readability. Let's try to look at what each one is about and who & why finds this particular kind of readability relevant.

1. Implementation readability

Let's imagine we're developing client-server application for project management. The part we'll be dealing with is the server-side processing of a request to add new issue to existing project. By looking at the example code, we'll see how and why implementation readability plays out in practice. Without further ado, ladies and gentlemen, the code:

public void AddNewIssueToProject(Request request, Response response)
{
  try
  {
    if(!request.ContainsKey("senderIp") 
       || string.IsNullOrWhiteSpace(request["senderIp"] as string))
    {
      response.Write("Wrong sender IP");
      return;
    }

    if(!request.ContainsKey("projectId") 
       ||  string.IsNullOrWhiteSpace(request["projectId"] as string))
    {
      response.Write("Invalid project ID");
      return;
    }

    if(!request.ContainsKey("issueData") || 
      request["issueData"] as Dictionary<string, string> == null)
    {
      response.Write("issue data not passed");
      return;
    }

    var issueData 
      = request["issueData"] as Dictionary<string, string>;

    if(!issueData.ContainsKey("Title") || 
        string.IsNullOrWhiteSpace(issueData["Title"]))
    {
      response.Write("No title for issue supplied. " +
        "Issue title must be at least one readable character");
      return;
    }

    if(!issueData.ContainsKey("Content") || 
        string.IsNullOrWhiteSpace(issueData["Content"]))
    {
      response.Write("No content for issue supplied. " +
        "By our policy, every issue must be described with details");
      return;
    }

    if(!issueData.ContainsKey("Severity") 
       || string.IsNullOrWhiteSpace(issueData["Severity"]))
    {
      response.Write("No severity supplied, " + 
        "although the form forces this." +
        " Are you trying to send us raw HTTP request?");
      return;
    }

    var projectId = request["projectId"] as string;
    var issueTitle = issueData["Title"];
    var issueContent = issueData["Content"];
    var severity = issueData["Severity"];

    var queryString = string.Format(
      "INSERT INTO ProjectIssues VALUES ({0}, '{1}', '{2}', '{3}')",
      projectId, issueTitle, issueContent, severity);

    var query = new Query(queryString);
    using(var connection = DatabaseConnection.Open("dbName=Abcdef"))
    {
      query.ExecuteThrough(connection);
    }

    response.Write("Everything's OK!");
  }
  catch(Exception e)
  {
    response.Write("Unexpected error, see log for details");
    _log.Error(e);
  }
}

While this snippet may seem like a classic spaghetti code to those of you who are more design-infected, there is actually a significant benefit behind it. The benefit is that without looking anywhere else, we can deduce the state of the method variables in every line and what precisely will happen in the system when we run this code. Because the method is made up mostly of primitive or library constructs, we're able to "debug" it with our eyes only, without jumping here and there to gather the parts of the functionality necessary to "understand" the code.

Wait, did I just write "understand"? What kind of "understanding" are we talking about here? Well let's take the following line as example:

    var projectId = request["projectId"] as string;

When reading this code, by the time we arrive at this line we already know that some values are put inside the dictionary and that they're non-null and not some other value like empty string. What are "dictionary", "null" and "empty string"? They're implementation details! Are they connected to the domain of request processing? No. Do they help describe the high-level work-flow? No. Do they help us understand the requirements? Not much. Can we read from the code what steps does this method consists of or where each step begins and where it ends? No, we can extract it somehow by comparing the code to our domain knowledge or experience (e.g. most of us know that each request coming to the server has to be validated), but again, this is something extracted from the code, not something that's already there.

So, the conclusion is that this style of writing is better at describing how the code works, while doing a poor job of describing what the code is for.

Benefits of implementation readability

So, who benefits from having a high implementation readability? Well, there are times when we're given some assignments in a code we don't know. Let's say that our boss told us to fix a bug in an application that should write string "abcd" to Windows registry, but it writes "abc". Now, we don't care about the big picture - all we care for is that we know what the single external behavior is and what it should be instead (we don't even care why), so we search for this one line in the code that is responsible for the behavior to replace it with something else. While searching, everything that is not the sought construct (including design and domain knowledge), is in our way. From this point of view, the ideal situation would be to have the whole application in a single source file so that we can use our text editor to search it for the word "Registry", then examine each occurrence and make the fix. In other words, we're acting as a little smarter "search and replace" mechanisms. The single-responsibility classes just get in our way and make us "pointlessly" navigate through a web of objects we neither want nor need to understand (some would say that we're on level 1 or 2 of Dreyfus model of skill acquisition).

While cases such as this one happen, we must realize that they're not the cases we're trying to optimize for. In any but the simplest cases, making a novice go into a piece of code to make changes without understanding of domain or design will lead to degradation of the code and probably few bugs (when someone is not aware of the big picture, they might fix a bug in a way that introduces another bug somewhere else, because of another scenario they didn't have any idea of). So the case we want to optimize for is someone going into the code with a need to understand the domain and the design first before making a change. It's best if they can derive at least part of this knowledge from the code itself and easily map parts of domain knowledge to places in the code. And this is how we arrive at...

2. Design readability

Part of good object-oriented design is implementation hiding, i.e. we want to hide how a particular piece of functionality is implemented so that we can change it later. Also, when we're equipped with domain and design knowledge, we want this knowledge stand out, not to be obfuscated by implementation details. To give you a quick example of what I mean: when talking about web server session storage, we say that "a user is assigned a persistent session", not that "a database holds a serialized hashtable indexed by user ID". Thus, we want to see the first clearly visible in the code, not the latter. Otherwise, readability is hurt.

Let's now take a little piece of refactored request handling code that we began with and see whether we can see any improvements:

class AddingNewIssueToProject : Scenario
{
    UserInteraction _user;
    AddIssueToProjectRequest _addIssueToProjectRequest;
    PersistentStorage _issueStorage;

    public AddingNewIssueToProject (
      AddIssueToProjectRequest requestForAddingIssueToProject, 
      UserInteraction userInteraction,
      PersistentStorage issueStorage)
    {
      this._requestForAddingIssueToProject 
        = requestForAddingIssueToProject;
      this._user = userInteraction;
      this._issueStorage = issueStorage;
    }

    public void GoThrough()
    {
      try
      {
        _requestForAddingIssueToProject.Validate();
        Issue issue = _requestForAddingIssueToProject.ExtractIssue();
        issue.StoreInside(_issueStorage);
        issue.ReportSuccessfulAdditionTo(_user);
      }
      catch(ScenarioFailedException scenarioFailed)
      {
        _user.NotifyThat(scenarioFailed);
      }
   }
}

As you can see, there's almost no "implementation" here. No strings, no nulls, no dictionaries, lists, data structures, no addition, subtraction, multiplication etc. So how can it be readable as it tells very little of the external application behavior? Count with me:

  1. The class name is AddingNewIssueToProject, which says that this class is all about this single scenario (in other words, when something is wrong with different scenario, this is not the place to look for root cause). Moreover, it implements a Scenario interface, which gives us a clue that each scenario in the application is modeled as object and we can most probably find all scenarios supported by the application by searching for classes implementing Scenario interface.
  2. It depends on three interfaces: UserInteraction, AddIssueToProjectRequest and PersistentStorage, which suggest that the scope of the scenario is: communication with user, working with the user-entered data and saving data to persistent storage.
  3. The GoThrough() method contains the sequence of steps. We can clearly see which steps the scenario consists of and what is their order, e.g. looking at this method, we are sure that we're not trying to save invalid Issue object, since storing issue is after validating correctness.
  4. Looking at the invocation of ExtractIssue() method, we can see that the new issue that's being saved is made of data entered by user.
  5. The object _issueStorage is of type PersistentStorage, suggesting that, after the issue is saved, we'll be able to retrieve it later, most probably even after the server restart.
  6. Each of the steps of the scenario is allowed to fail (by throwing ScenarioFailedException) and the user is immediately notified of the failure.
  7. If the adding issue scenario ever needs to perform any additional step in the future, this is the place where we'd start adding this change.

Not bad for just few lines of code that contain practically no implementation details, huh?

Which one to prefer?

Personally, I strongly prefer design readability, i.e. I want to draw as much domain knowledge and design constraints knowledge from the code (not to be mistaken with overdesign) as possible. One reason for this is that design readability makes it much easier to introduce changes that are aligned with the "spirit" of the code that's already in place and such changes are usually much more safe than hacking around. Another reason is that implementation readability is impossible to maintain over the long haul. Let's take the following snippet from our example:

if(!issueData.ContainsKey("Severity") 
   || string.IsNullOrWhiteSpace(issueData["Severity"]))
{
  response.Write("No severity supplied, " + 
    "although the form forces this." +
    " Are you trying to send us raw HTTP request?");
  return;
}

This is a part of code responsible for adding new issue, but we can imagine it making its way to scenario for editing existing issue as well. When this happens, we have two choices:

  1. Copy-paste the code into the second method, creating duplication and redundancy (and leading to situations where one change needs to be made in more than one place, which, according to Shalloway's Law is asking for trouble)
  2. Extracting the code at least to a separate method that already defeats implementation readability partially, because each time you need to understand what piece of code does, you need to understand an additional method. All you can do is to give this method an intent-revealing name. Anyway, introducing new methods just to remove redundancy leaves us with with code where sections of implementation details are mixed with sections of domain work-flows stated in terms of calling domain-specific methods on domain-specific objects. From such code, we can easily deduce neither full work-flows, nor all implementation details. Such code is better known under the name of Spaghetti Code :-),

An object oriented system is not a set of streams of primitive instructions. It is a web of collaborating objects. The more complex the system, the more this metaphor becomes visible. That's why, instead of trying to put all implementation in one place to make it "debuggable", it's better to create a cohesive and readable description of that collaboration. A description that reveals domain rules, domain workflows and design constraints.

Of course, the choice is yours. Until you decide to do TDD, that is. That's because TDD strongly encourages clean design and separation of domain and design from implementation details. If good design guidelines are not followed, tests get longer and longer, slower and slower, more and more test logic is repeated throughout the tests etc. If that's where you are now, better revise your approach to design!

What do you think?

I'm dying to know what your opinions and observations on these topics are. Do you find the distinction between implementation and design readability useful? Which one you prefer? Please let me know in the comments!

Monday, 4 March 2013

The Two Main Techniques in Test-Driven development, part 2

Today, I'd like to continue where I left last time. As you might remember, the first post was about Need-Driven Development and this one's gonna be about a technique that's actually older and it's called Triangulation.

Triangulation

History

The first occurence of the term triangulation I know about is in Kent Beck's book Test Driven Development: By Example where Kent describes it as the most conservative technique of test-driving the implementation. It's one of the three core techniques of classic TDD.

Description

The three approaches to test-driving the implementation and design described in Kent's book are:

  1. Write the obvious implementation
  2. Fake it ('till you make it)
  3. Triangulate

Kent describes triangulation as the most conservative technique, because following it involves the tiniest possible steps to arrive at the right solution. The technique is called triangulation by analogy to radar triangulation where outputs from at least two radars must by used to determine the position of a unit. Also, in radar triangulation, the position is measured indirectly, by combining the following data: range (not position!) measurement done by the radar and the radar's own position (which we know, because we put the radar there).

These two characteristics: indirect measurement and using at least two sources of information are at the core of TDD triangulation. Basically, it says:

  1. Indirect measurement: Derive the design from few known examples of its desired external behavior by looking at what varies in these examples and making this variability into something more general
  2. Using at least two sources of information: start with the simplest possible implementation and make it more general only when you have two or more examples

Triangulation is so characteristic to the classic TDD that many novices mistakenly believe TDD is all about triangulation.

Example

Suppose we want to write a method that creates an aggregate sum of the list. Let's assume that we have no idea how to design the internals of our custom list class so that it fulfills its responsibility. Thus, we start with the simplest example:

[Test]
public void 
ShouldReturnTheSameElementAsASumOfSingleElement()
{
  //GIVEN
  var singleElement = Any.Integer();
  var listWithAggregateOperations 
    = new ListWithAggregateOperations(singleElement);

  //WHEN
  var result = listWithAggregateOperations.SumOfElements();

  //THEN
  Assert.AreEqual(singleElement, result);
}

The naive implementation can be as follows:

public class ListWithAggregateOperations
{
  int _element;

  public ListWithAggregateOperations(int element)
  {
    _element = element;
  }

  public int SumOfElements()
  {
    return _element;
  }
}

It's too early to generalize yet, as we don't have another example. Let's add it then. What would be the next more complex one? Two elements instead of one. As we can see, we're beginning to discover what's variable in our design. The second spec looks like this:

[Test]
public void 
ShouldReturnSumOfTwoElementsAsASumWhenTwoElementsAreSupplied()
{
  //GIVEN
  var firstElement = Any.Integer();
  var secondElement = Any.Integer();
  var listWithAggregateOperations 
    = new ListWithAggregateOperations(firstElement, secondElement);

  //WHEN
  var result = listWithAggregateOperations.SumOfElements();

  //THEN
  Assert.AreEqual(firstElement + secondElement, result);
}

And the naive implementation will look like this:

public class ListWithAggregateOperations
{
  int _element1 = 0;
  int _element2 = 0;

  public ListWithAggregateOperations(int element)
  {
    _element1 = element;
  }

  //added
  public ListWithAggregateOperations(int element1, int element2)
  {
    _element1 = element1;
    _element2 = element2;
  }

  public int SumOfElements()
  {
    return _element1 + _element2; //changed
  }
}

Now the variability in the design becomes obvious - it's the number of elements added! So now that we have two examples, we see that we have redundant constructors and redundant fields for each element in the list and if we added a third spec for three elements, we'd have to add another constructor, another field and another element of the sum computation. Time to generalize!

How do we encapsulate the variability of the element count so that we can get rid of this redundancy? A collection! How do we generalize the addition of multiple elements? A foreach loop through the collection!

First, let's start with writing a new, more general unit spec to showcase the new desired design (the existing two specs will remain in the code for now):

[Test]
public void 
ShouldReturnSumOfAllItsElementsWhenAskedForAggregateSum()
{
  //GIVEN
  var firstElement = Any.Integer();
  var secondElement = Any.Integer();
  var thirdElement = Any.Integer();
  var listWithAggregateOperations 
    = new ListWithAggregateOperations(new List<int>
      { firstElement, secondElement, thirdElement});

  //WHEN
  var result = listWithAggregateOperations.SumOfElements();

  //THEN
  Assert.AreEqual(firstElement + secondElement + thirdElement, result);
}

Note that we have introduced a constructor taking a list of arbitrary number of elements instead of just the values. Time to accommodate it in the design and bring the generalization we have just introduced in our spec into the implementation:

public class ListWithAggregateOperations
{
  List<int> _elements = new List<int>();

  public ListWithAggregateOperations(int element)
    : this(new List<int>() { element }) //changed
  {
  }

  public ListWithAggregateOperations(int element1, int element2)
    : this(new List<int>() { element1, element2 }) //changed
  {
  }

  //added
  public ListWithAggregateOperations(List<int> elements)
  {
    _elements = elements;
  }

  public int SumOfElements()
  {
    //changed
    int sum = 0;
    foreach(var element in _elements)
    {
      sum += element;
    }
    return sum;
  }
}

As we can see, the design is more general and I made the two existing constructors as a simple delegation to a new, more general one. Also, the first two specs ("one element" and "two elements") still pass with the new, more general implementation under the hood, meaning that we didn't break any existing behavior. Thus, it's now safe to remove those two specs, leaving only the most general one. Also, we can remove the redundant constructors, leaving the implementation like this:

public class ListWithAggregateOperations
{
  List<int> _elements = new List<int>();

  public ListWithAggregateOperations(List<int> elements)
  {
    _elements = elements;
  }

  public int SumOfElements()
  {
    int sum = 0;
    foreach(var element in _elements)
    {
      sum += element;
    }
    return sum;
  }
}

And voilà! We have arrived at the final, generic solution. Note that the steps we took were tiny - so you might get the impression that the effort was not worth it. Indeed, this example was only to show the mechanics of triangulation - in real life, if we encountered such simple situation we'd know straight away what the design would be and we'd start with the general specs straight away and just type in the obvious implementation. Triangulation shows its power in more complex problems with multiple design axes and where taking tiny steps helps avoid "analysis paralysis".

Principles

These are the main principles of Triangulation:
  1. Start writing specs from the simplest and most obvious case, increasing the complexity of the specs and the generality of implemntation as you add more specs.
  2. Generalize only when you have at least two examples that show you which axis of design change needs to be generalized.
  3. After arriving at the correct design, remove the redundant specs (remember, we want to have one spec failing for single reason)

Related Concepts

Inside-out development

Triangulation is a core part of inside-out TDD style, where one uses mocks sparingly and focuses on getting the lower-layer (i.e. the assumptions) right before developing a more concrete solution on top of it.

Can triangulation be used as a part of outside-in development (as described last time)? Of course, although it's probably used less often. Still, when you have a piece of functionality with well-defined inputs and outputs but don't know what the design behind it could be, you can go ahead and use triangulation whether you're developing outside-in or inside-out.

Acceptance tests/specifications

Even when doing Need-Driven Development using mocks etc., triangulation can be very useful at the acceptance level, where you can try to derive the internal design of whole module based on the tests that convey your understanding of domain rules.

Applicability

As I stated before, triangulation is most useful when you have no idea how the internal design of a piece of functionality will look like (e.g. even if there are work-flows, they cannot be easily derived from your knowledge of the domain) and it's not obvious along which axes your design must provide generality, but you are able to give some examples of the observable behavior of that functionality given certain inputs. These are usually situations where you need to slow down and take tiny steps that slowly bring you closer to the right design - and that's what triangulation is for!

Personally, I find triangulation useful when test-driving non-trivial data structures.