40

Today we were training TDD and found the following point of misunderstanding.

The task is for the input "1,2" return sum of numbers which is 3. What I have written (in C#) was:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]); //task said we have two numbers and input is correct

But other guys preferred to do it other way. First, for input "1,2" they made added the following code:

if (input == "1,2")
   return 3;

Then they introduced one more test for input "4,5" and changed implementation:

if (input == "1,2")
   return 3;
else if (input == "4,5")
   return 9;

And after that they said "Okay, now we see the pattern" and implemented what I initially did.

I think the second approach better fits the TDD definition but... should we be so strict about it? For me it is okay to skip trivial baby steps and combine them into "twinsteps" if I am sure enough that I won't skip anything. Am I wrong?

Update. I have made a mistake by not clarifing it was not the first test. There already were some tests so "return 3" actually wasn't the simplest piece of code to satisfy the requirement.

9
  • 27
    So tiny that my coworkers gush "Ooahhh dazso cuuuuuute" Sep 22, 2011 at 7:47
  • 6
    @Adel: Almost choked on my breakfast, keyboard now full or spittle and crumbs Sep 22, 2011 at 8:06
  • 2
    @Adel, as for non-native speaker, it is rather difficult for me to understand this humour but I guess your coworkers like the question :) Sep 22, 2011 at 8:47
  • 8
    @Idsa: It's transposing a coworkers response when shown a childs first steps "Ooahhh dazso cuuuuuute" = "Oh that is so cute" (spoken in a sing-song-isn't-that-very-cute voice), with their response when seeing Unit Tests written by Adel, looking at the Unit Tests baby steps they say "Oh that's so cute". Reaction to a - real - babies steps = reaction to unit tests "baby steps". Sep 22, 2011 at 9:00
  • 3
    @Binaryworrier wish I could give you real points for taking the time to explain the parentess Sep 22, 2011 at 11:55

10 Answers 10

32

Write the simplest code that makes the tests pass.

Neither of you did that, as far as I can see.

Baby Step 1.

Test: For the input "1,2" return sum of numbers which is 3

Make the test fail:

throw NotImplementedException();

Make the test pass:

return 3;

Baby Step 2.

Test: For the input "1,2" return sum of numbers, which is 3

Test: For the input "4,5" return sum of numbers, which is 9

Second test fails, so make it pass:

numbers = input.Split(',');
return int.Parse(numbers[0]) + int.Parse(numbers[1]);

(Way simpler than a list of if...return)

You can certainly argue Obvious Implementation in this case, but if you were talking about doing it strictly in baby steps then these are the correct steps, IMO.

The argument is that if you don't write the second test then some bright spark could come along later and "refactor" your code to read:

return input.Length; # Still satisfies the first test

And, without taking both steps, you have never made the second test go red (meaning that the test itself is suspect).

8
  • Regarding your input.Length example, with same success I can imagine some mad incorrect implementation which won't be caught by both tests Sep 22, 2011 at 7:48
  • @Idsa - Yes, absolutely, and the more tests you write, the more mad the implementation has to be. input.Length isn't that far fetched, especially if the input to the method happens to be a measurement from some file somewhere and you inadvisedly called your method Size().
    – pdr
    Sep 22, 2011 at 7:54
  • 6
    +1. With respect to how to learn TDD, this is the right way. Once you have learned it, you may sometimes go directly to the obvious implementation, but to get a feel for TDD, this is much better. Sep 22, 2011 at 14:28
  • 1
    I have a question concerning the "test" itself. Would you write a new test for input "4,5", or modify the original test?
    – mxmissile
    Sep 22, 2011 at 19:51
  • 1
    @mxmissile: I would write a new test. It doesn't take a whole lot of time and you end up with twice as many tests to protect you when you do refactoring later.
    – pdr
    Sep 22, 2011 at 20:21
54

I think the second way is mind numbingly stupid. I see the value in making small enough steps, but writing those tiny zygote (can't even call them baby) steps is just asinine and a waste of time. Especially if the original problem you're solving is already very small by it's own.

I know it's training and it's more about showing the principle, but I think such examples do TDD more bad than good. If you want to show the value of baby steps, at least use a problem where there is some value in it.

5
  • +1 and thanks for making me look up and learn a new word (asinine) Sep 22, 2011 at 7:59
  • 15
    +1 for calling it numbingly stupid. TDD is all nice and such, but like with any modern hyped programming technique you should take care not to get lost into it.
    – stijn
    Sep 22, 2011 at 7:59
  • 2
    "Especially if the original problem you're solving is already very small by it's own." - If the input were two ints to be added together, I would agree with this, but I'm not convinced when it's "split a string, parse two ints from the result and add them." Most methods in the real world aren't much more complicated than that. In fact, there should be more tests to come, to cover edge cases like finding two commas, non-integer values, etc.
    – pdr
    Sep 22, 2011 at 8:13
  • 4
    @pdr: I agree with you that there should be more tests to handle the edge cases. When you write them and notice that your implementation needs to change to handle them, by all means do that. I guess I just have an issue with taking zygote steps to the first happy-path, "obvious implementation", instead of just writing that down and going from there. I don't see the value in writing out an if statement that every fiber in my body knows is just going to disappear the next moment. Sep 22, 2011 at 9:41
  • 1
    @ChristopheVanfleteren: When Beck describes obvious implementation, he uses the sum of two ints as an example and still throws in a huge dramatic warning about how you're going to die of shame if your pair / reviewer can think of simpler code that makes the test pass. That is an absolute certainty if you only write one test for this scenario. Also, I can think of at least three "obvious" ways to solve this problem: split & add, replace the comma with + and evaluate, or using regex. The point of TDD is to drive you to the correct choice.
    – pdr
    Sep 22, 2011 at 10:38
20

Kent Beck covers this in his book, Test Driven Development: By Example.

Your example indicates an 'obvious implementation' - you want to return the sum two input values, and this is a fairly basic algorithm to achieve. Your counter-example fall into 'fake it until you make it' (although a very simple case).

Obvious implementation can be much more complicated than this - but basically it kicks in when the specification for a method is pretty tight - for example, return a URL encoded version of a class property - you don't need to waste time with a bunch of faked encodings.

A database connection routine, on the other hand, would need a bit more thought and testing so there is no obvious implementation (even if you might have written one several times already on other projects).

From the book:

When i use TDD in practice, I commonly shift between these two modes of implementation, When everything is going smoothly and I know what to type, I put in Obvious Implementation after Obvious Implementation (running the tests each time to ensure that what's obvious to me is still obvious to the computer). As soon as I get an unexpected red bar, I back up, shift to faking implementations, and refactor to the right code. When my confidence returns, I go back to Obvious Implementations.

18

I see this as following the letter of the law, but not its spirit.

Your baby steps should be:

As simple as possible, but no simpler.

Also, the verb in the method is sum

if (input == "1,2")
   return 3;

isn't a sum, it's a test for specific inputs.

4

To me it seems fine to combine several trivial implementation steps into one slightly less trivial - I do that all the time too. I don't think one needs to get religious about following TDD everytime to the letter.

OTOH this applies only for really trivial steps like the above example. For anything more complex, which I can't fully hold in my mind at once and/or where I am not 110% sure about the outcome, I prefer to go one step at a time.

1

When first setting out down the road of TDD the size of steps can be a confusing issue, as this question illustrates. A questions I often asked myself when I first started writing test driven applications was; Is the test I am writing helping to drive my applications development? This may seem a trivial and unrelated to some but hang in there with me for a moment.

Now when I set out to write any application I will usually start with a test. How much of a step that test is largely relates to my understanding of what it is I am trying to do. If I think I have pretty much got the behaviour of a class in my head then the step will be a big one. If the problem I am trying to solve is a lot less clear then the step might simply be that I know there is going to a method named X and that it will return Y. At this point the method won't even have any parameters and there is a chance that the name of the method and the return type will change. In both cases the tests are driving my development. They are telling me things about my application:

Is this class that I have in my head actually going to work?

or

How the hell am I even going to do this thing?

The point is that I can switch between big steps and little steps in the blink of an eye. For example if a big step doesn't work and I can't see an obvious way around it I will switch to a smaller step. If that doesn't work I will switch to an even smaller step. Then there are other techniques such as triangulation if I get really stuck.

If like me you are a developer and not a tester then the point of using TDD is not to write tests but to write code. Don't get hung up on writing loads of small tests if they aren’t given you any benefits.

I hope you enjoyed your training an stick with TDD. IMHO if more people were test infected then the world would be a better place :)

1

In a primer about unit testing i read the same approach (steps that look really, really tiny), and as an answer to the question "how tiny should they be" something i liked, which was (paraphrased) like this:

It's about how confident you are that the steps work. You can make real big steps if you want. But, just try it for some time and you will find a lot misguided confidence in places you take it for granted. So, the tests help you to build a fact-based confidence.

So, maybe your collegue is just a little shy :)

1

I'm agreeing with the people saying that neither is the simplest implementation.

The reason the methodology is so strict is that it obliges you to write as many relevant tests as possible. Returning a constant value for one test case and calling it a pass is fine because it forces you to go back and specify what you really want in order to get anything other than nonsense from your program. Using such a trivial case is shooting yourself in the foot in some respects, but the principle is that mistakes creep into the gaps in your specification when you try to do 'too much' and rendering down the requirement to the simplest possible implementation ensures that a test must be written for each unique aspect of behaviour that you actually want.

1
  • I have added an update regarding "returning a constant value" Sep 22, 2011 at 16:41
0

Isn't the whole point that the implementation of the method is irrelevant, as long as the tests succeed? Extending the tests will fail quicker in the second example, but can be made to fail in both cases.

2
  • 2
    It is irrelevant if you totally don't care about wasting your time Sep 22, 2011 at 13:28
  • The implementation isn’t irrelevant because there is readability, maintainability, and your reputation of not being a complete numpty. Worst, I don’t care that the unit tests succeed if I look at the code and know that anything outside the unit tests will fail.
    – gnasher729
    Dec 28, 2022 at 14:50
-1

Here’s a sensible sequence: Make it work for two ints separated by commas. Make it work as specified for two ints separated by something else. Make it work when preceded or followed by white space. Make it work as specified for 0, 1, 3, 4 or more ints. Make it work according to the spec for decimal numbers. With exponents. With units. With mixed units like 3 pound, six ounces. And so on.

Returning a hard coded 3 when the input is “1,2” and nothing for other inputs is just stupid.

3
  • 2
    What your calling for is defined as Feature Creep. It's when you make up requirements rather than gather them. It's not considered a good thing. The hard coded 3 however comes directly from a requirement. Writing easy to read, brain dead tests, before any code, that mindlessly follow a requirement, and assume no pattern, as your first tests is a tradition in TDD. You can see it in the KATAs Dec 28, 2022 at 15:24
  • Nonsense. Everything I listed has to work (according to the spec). So everything needs a unit test. So everything needs code that makes the unit test work.
    – gnasher729
    Dec 28, 2022 at 15:56
  • 1
    Why on Earth would anyone introduce mixed units into this? Feature creep indeed. Dec 28, 2022 at 16:01

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.