TDD LIKE YOU MEAN IT - 05

TDD LIKE YOU MEAN IT - 05

A second requirement second test

As a refresher, let's remind ourselves of the requirements of FizzBuzz

Write a method that takes a whole number and returns a text representation and has the following behavior for whole numbers above 0 (does not include 0). We don't have to add any bounds checking in this.
1. Return the string form of the number
   * 1 as input returns "1"
2. If input is a multiple of 3 return "Fizz"
   * 6 as input return "Fizz"
3. If input is a multiple of 5 return "Buzz"
   * 10 as input return "Buzz"
4. If input is a multiple of 3 and 5 return "FizzBuzz"
   * 30 as input return "FizzBuzz"

and we're working on 2. If input is a multiple of 3 return "Fizz".

We've previously implemented the first test where 3 gives us "Fizz". And we encountered a hiccup which helped us explore how to hand a test failing unexpectedly. It's great to practice that as well. Often we can set up to have less frequent conditions happen so that we can practice fixing them.

Having gone through the refactor, it's Time for a new test in our flow chart.

We're working on a requirement and I don't think 6 will give us "Fizz". Which is a totally doable test.

What do you think is a good intent? //Input 6 Gives Fizz sounds reasonable to me. I mean... I hope so... I wrote it.

main 9bbd24e

What test scaffolding should we use? The quickest route we have is copy and paste. I like to get things done quickly so that we can get to a point to refactor and clean it all up. Especially when acting as the driver in a mob. If the navigator says "Let's create a new test that expects Fizz when 6 is passed in" I want to be able to get that into the computer as fast as possible, but safely.

Let's do that and we can practice the inner->outer change.

  • We first copy and paste the method main 754e969
  • Then change the 3 to 6 main 113f38c
  • And lastly change the name to match our intent

When we run all the tests, we see the failure for the reason we expect which is clear in the message.

tdd_tests_015

Ya like how I jumped us through a few of the flow chart steps there? It's really easy to jump to far; be cautious of that - And practice not jumping to far. Practice explicitly going step by step in the flow chart. It'll help you catch yourself when you're working and go too far. It'll feel wrong. Listen to that, check what you're doing.

And now that we're in a RED state, we get to modify the code to make it pass.

Since we're in an existing method, we will add a condition for the test input before we return what the assert expects.

main f067738

We can see the test pass now

tdd_tests_015

Simplest?

Is adding a new line with an if and a return the simplest? Would you accept a "yes" and "no" for an answer?

Simplest isn't least code. We might be able to get away with less code. There is a way we could very easily do it with less code. A high enough version of C# can write an if condition like if(source is 3) then we could just add or 6. It's MUCH less code.

We're practicing how to add functionality safely. It's safer to practice adding a whole new line with the input and output specified. It'll help highlight patterns. It will let us manipulate the code in a away that can make patterns more obvious. Practice and exercises always have a "quicker" way to get the answer - but the answer isn't what we're after - We're practicing.

If this "we're practice" reminder comes up a few times, it's because it's a good reminder. We're doing things in a simple exercise that will help us in complex code bases.

Continuing!

With all tests passing, we can look for refactor opportunities! There's an interesting one that's often debated when we do this as a weekly+ practice.

Our current requirement is that a multiple of 3 returns Fizz. I like to think I'm decent at some mathematics - but not everyone is. Not everyone can quickly see that 6 is a multiple of 3. The tests and code isn't clearly communicating the intent. It's hidden in the single hard coded number.

This is part of the reason I also choose simple number. I could choose 7_023_957 ... It's a multiple of 3. Would work.
I'm sure we can all agree that it is not a good choice because it does not clearly communicate that it is a multiple of 3. What would? If we REALLY wanted to do something silly, how could we clearly communicate (which is another way phrasing for the 'Expresses Intent') that it is a multiple of 3?
What about 2_341_319 * 3? It's clearly communicating that it's a multiple of 3. If we struggle to make sense of a number - What if someone else is going to struggle with 6? We should make it clearly express it's intent better.

Which we can do by making the test set the sourceInput to 2 * 3 main 0fef4bc.
We should also do this to our code main 33b3744.

If we look at our code now we have lost some similarity

public string Transform(int source)
{
    if (source == 2 * 3) return "Fizz";
    if (source == 3) return "Fizz";
    return source.ToString();
}

What can we do to get these lines  looking more similar? If you said to expand == 3 to be == 1 * 3 then we're thinking the same thing. main 1dbc720

public string Transform(int source)
{
    if (source == 2 * 3) return "Fizz";
    if (source == 1 * 3) return "Fizz";
    return source.ToString();
}

If we continue looping through our '4 Rules of Simple Design' we'll definitely see that we can easily "Minimize Elements" here.
This is why the order of these rules are so important. We've created more elements here so the code is better at expressing intent. That's more important that reducing elements. Much like we wouldn't want to undo  2_341_319 * 3, we shouldn't undo the simpler example.

If we're happy with this, we can also update our first test for this requirement to set the sourceInput to 1*3. Since I am happy with it, I'll definitely be doing that main 18f74f8.

Just start like that?

I've been asked why I don't just start with 1 * 3 and 2 * 3 - Because I treat it as if I don't know yet. I want to practice updating the tests to enhance their intent. I want to practice having to make other changes to keep things similar. I want to practice making a cascade of changes because I'm discovering things in the code.

Another huge reason is that these practices come from doing FizzBuzz SO. MANY. TIMES. We discovered these things and had to keep re-discovering them and sharing them in the group. I'm sure we tried a great many way to do these little things. I just remember what we tended to use. Even in those practices we'd do it as a refactor.
We don't want to over-engineer the code until the code shows us that it's going to have value in the code. 1*3 is no value in the code UNTIL we realize that 2*3 has value. And 1*3's value is different than 2*3. So why would I do 1*3 to start? There's no value.
I have started with 2*3. It's OK; but it doesn't actually match our intent, so ... we'd be deviating a bit from that. If our intent is stated as 2*3... Well... I think that's gold plating the code before we even write the code and is a silly thing to do.
Our intent is to pass in 6, that's what we code. We then consider our code.

STORY TIME
The practice of actually considering the code is critical. When I do TDD training with teams, we'll do this exercise twice weekly. Always FizzBuzz.
There's always a few devs that figure out the mechanics of the exercise. They can FLY through it. They don't understand why each step though. When I stop and ask questions about why they're making some changes... they struggle to answer it. They don't consider the code. They memorize how to finish.

I practice considering the code. It's not "6 can be 2*3, I'll do that". I genuinely consider it every time I go through FizzBuzz. If I'm using FizzBuzz to practice something other than TDD LIKE I MEAN IT; I may not split it. It's something I consider each time I do the exercise.

The same applies to the name of the method and argument. Writing this out is the first time I've used Transform and source. At least I don't remember any other time. Having done FizzBuzz SO MANY TIMES, you'd think threre'd be standard names I use, but no. That's not the point. The point is to practice considering the name. Then pick something and keep going.

If you go through the motions of consideration without actually considering, you're losing out on so much practice that we can do to help ourselves improve.

I consider if I should use //ARRANGE, //ACT, and //ASSERT comments each time. I consider what type of capitalization they should have.  Considering these minute, and often trivial, details of the code will help you feel comfortable spending time considering bigger things. Taking the time to think up a few additional options is minimal when compared to having to update a bunch of code because something simple was missed.

Pattern?

Do we see a pattern in the code? Yes.

if (source == 2 * 3) return "Fizz";
if (source == 1 * 3) return "Fizz";

These lines have something going on. What exactly... a little harder to say with just two examples. Many engineers I've talked to have independently found that having three similar pieces of code before refactoring. Two can be coincidence, three is where a pattern is probably there. The most times I've copy/pasted is five. The sixth time is when I was confident of an abstraction. It was pretty complex and the 6th time didn't modify the logic, just the values.

Sandi Metz puts it very eloquently

duplication is far cheaper than the wrong abstraction

and

prefer duplication over the wrong abstraction

Let the code tell you it needs abstraction. It's not asking for it yet.

In our code - we modified the structure of it. Very little, very trivial, but the structure was modified. If we are modifying the structure of the code, it's unlikely we have enough knowledge to create a good abstraction.

That Comment

But we can remove the human language intent comment. That doesn't need to hang around anymore. main 4471ef4

Next Time

That's a wrap on our second test for our second requirement.

Next time we'll get our third test in place and use that triangulation to refactor to a generalized solution.

Show Comments