TDD LIKE YOU MEAN IT - 01

TDD LIKE YOU MEAN IT - 01

Preface?

I read some TDD books, got annoyed with them and then did the same TDD exercise those books had, the Money example. Those posts will be up after this series. They need more clean up.

Doing that, I realized I was doing a little higher level than HOW TO TDD. It felt like the Money problem was the focus, not TDD. I wasn't getting across "How I TDD" like I wanted to. I think it's an informative bit of writing. I go off on lots of tangents. I go on fewer tangents in these posts, but still some.

I very specifically wanted to share my inner process when I sit to do TDD. I wanted to document it. You'll see that I even created a flow chart! It's not exactly what I wanted... the tool limited me a little in how I could connect boxes. It's good enough though.

This is my exercise in making "my thoughts" of how to TDD available w/o having me providing you coaching on it.

Hopefully you enjoy the series. As always, I apologize for how much I suck at writing; I didn't re-read any of this. It's straight from my brain to your brain... and that's gotta hurt.

What is TDD?

If you're reading this, I kinda suspect you know what TDD is... And I suck at summarizing it aside from a the base RED-GREEN-REFACTOR loop.

TDD2

This entire blog series is my attempt to explain it.

If you need some refresher

I might update this "intro" over time... but since I have nothing now... Let's just dive in.

TDD Like You Mean It

I don't know who I first heard this phrase from; but my 5 minutes of internet-ing lead me to a guy named "Keith Braithwaite" as the earliest reference.

TDD like you mean it

I've heard it said from various technical/XP coaches that teach developers TDD. We want to practice it, "Like we mean it". This isn't just going through the motions of RED-GREEN-REFACTOR. Or as a lot of beginners to TDD do, RED-GREEN-REPEAT.
We started to practice TDD.  We treated it like the tool it is, and practiced using it. Practiced what to do during the REFACTOR phase.

Practiced doing them differently. Practiced again. And again. And again.

We started off doing all sorts of programming katas. Bowling, Baseball, Game of Life, and others. We practiced through mobbing, pairing, and sometimes solo.

I can't give a good recount of the process that we ended up doing that, but we settled on FizzBuzz as the "problem" we'd use. Mostly because it stopped being a problem. As I'll show before we start coding, I know the solution to FizzBuzz. I could type it with both hands tied behind my back.

Once we knew the solution to the problem, it became about the practice. We could focus on something and practice that, without any distraction of what code needed to be written to get the test to pass. We knew that. We know every single step, but we did deliberate practice. Steve Kuo was the driver of this transformation in the group.

We did our deliberate practice using FizzBuzz twice a week as a group. We practiced and explored and grew together. I learned a name for the type of group we developed while at DeliverAgile 2019 from Jessica Kerr in her key note. Symmathesy.

We really pushed each other to be better. Learned and grew beyond what I've done at other places. I also think that our development process of FAST Agile. We had the autonomy to do what we believed was the right thing, and for the amount of learning and growth the deliberate practice of our Symmathesy gave us... It was definitely the right thing.

Great things like that team don't always last. I hope I can be part of another one someday. That time and supportive pressure to be better shaped a lot of my thinking around software development that I continue to use today. I won't wane nostalgia about it, because we're here for a different purpose than my memories.

Much like being part of that team; Effective TDD will open a whole new world of coding, at least it did for me. Alone, TDD didn't do it; but it provides such a safety net in the code I was able to explore within the code. I could try things and know if they worked or not without trying to run end to end and breakpoints and that hassle. The things learned from exploring knowing functionality is stable accelerated us into being better developers.

This is going to be about how I do TDD as a deliberate practice. This deliberate practice builds up the 'muscle memory' to use these when we're doing actual work. It helps us overcome the urge to just write some code.

It's never perfect in work. It rarely is in our deliberate practice, which is why we continue to practice.

The problem is a lie

What we found through many different kata's is that the problem was what we focused on. We wanted to practice TDD, but we were really practicing solving different problems. We needed to remove the problem. That there is a problem is a lie - Know it so well that it's not a problem. This is the trick that was realized and got us all to deliberate practice. We don't want to solve a problem, we want to practice and get better with the skills and tools we use. We want these to be muscle memory when we have to focus on a problem.

We do need SOMETHING to code up while practicing; as mentioned, the one we fell to is the simple "FizzBuzz". FizzBuzz has a few simple rules that we use

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"

That's our non-problem to practice.

If you have never done FizzBuzz before - go do it now.

...

Stop reading, go implement FizzBuzz. Then come back.

...

Great - I'm assuming you did FizzBuzz - If not, really... go. Do it.

...

OK, I'm going to trust you did it. What'd you think? Which would be a better question if this was two way... Anyway... Now that you've done FizzBuzz, we can continue.

Go do it again. Do it a few more times.

...

Really. go do it until you can write the solution to FizzBuzz without any reference. OK, maybe just a few times if you're pressed for time.

...

Did you? Really?

... Okaaayyy..... I'll accept that... But I'm kinda suspicious.

The reason I'd like you to go implement FizzBuzz until you KNOW the solution, it guarantees we can focus on practicing TDD without using mental effort towards how to solve the problem while we practice. We KNOW the solution. I'm going to provide a C# solution here.

public string FizzBuzz(int input){
    if(input % (3 * 5) == 0) return "FizzBuzz";
    if(input % 3 == 0) return "Fizz";
    if(input % 5 == 0) return "Buzz";
    return input.ToString();
}

That's from memory. No IDE. Typed right into Typora. Some of that knowledge is C# itself; I couldn't do the same in F#, as an example. But I know the logic; I KNOW the solution. No matter what I use FizzBuzz to practice, I never have to solve it, I can focus on the practice.

TDD is the focus

For this - TDD is what we want to practice.

TDD is pretty straight forward

  1. Add a simple test
  2. Run all the tests and see the new one fail
  3. Make a simple change
  4. Run all the tests and see the new one pass
  5. Repeat until done

This is good; It is missing some of the elements I find very important.
TDD is simple to do, hard to master. As we go through the TDD Practice using FizzBuzz, I'll extend these to include some subtle details; which is why we practice. To improve on the subtle things.

Mob Programming is a great practice

While this isn't about mob programming, I'm going to be referencing mob and pair programming a bit. A lot of these techniques help communication. Even if they are intermediate stages, having that communication in place helps the flow of the mob/pair programming. It also helps me remember what I was doing if i get distracted. Oooo, shiny.

...

Your Environment

Set up whatever IDE and test environment you're going to be using. I'm going to use C#, Visual Studio 2022 and ReSharper. I incliude ReSharper because it does A LOT of the "typing" for me. I appreciate that.

TDD - Round 01

  • Add a simple test

That seems pretty straight forward; let's make a simple test!

First Test

What shall we test?

My favorite is to follow the requirements. Let's look at our first requirement

Return the string form of the number

What's a test for this? What's the input? What's the expected result?

What is the test we want to write? We pretty much get to pick. I like simple. What's the first valid input?

1.

What output does that result in?

"1"

While we have the input and output... that's not really a great description of the test itself. We want to be able to communicate the intent of the test. To ourselves as we write it, other developers we're working with, and anyone coming after us. So... What's this test doing?

Given an integer of 1 should return string of 1

And this gets us to the first subtle part. We now have a human language form of what we want the test to do. Since I'm writing this in English, we have the english version. Whatever version works for those you have to communicate with, use that.

Since we have this - Let's put it in the method that was already there when I generated a Test Project in Visual Studio.

[TestMethod]
public void TestMethod1()
{
    //Given an integer of 1 should return string of 1
}

Yes - As a comment. Now; everyone knows what we're trying to accomplish.

And we the start of our first test.

I don't name the test until I have the assert written. The first thing - `Write the ASSERT. Let's update our steps

My draft continued to use the bullet points for this post, but when I migrated to the blog host the indenting got lost. I'm just removing them. Not that you'd have noticed, but...

We actually had to do a small bit FIRST... We had to create the test method scaffolding.

We're really going step by step on this.

Looking at the human language of the test intent //Given an interger of 1 should return string of 1 it gives us our expectation. We now have what we want to validate the value is; "1".

This gets us to the next small step - Assert First. We should write the assert first. If we struggle to write a simple assert, we're not clear on what we're trying to test.

This is why I like to have the human language version first. If we can't get our intent down in less than a dozen words... we're probably failing the "simple" part of Add a simple test

Our assert here is going to use a little nomenclature I like to have in my tests, following the naming of assert method parameters.

[TestMethod]
public void TestMethod1()
{
	//Given an integer of 1 should return string of 1

    Assert.AreEqual("1", actual);
}

We can look at the signature and see the name of the parameters

public void AreEqual<T>(T expected, T actual) ...

I, and many others, often don't remember which way these go. As I typed the assert, I got it wrong. The intellisense corrected me. I like to use a tool to help me not make this mistake.

Fluent Assertions

https://fluentassertions.com/
A great tool that makes our asserts read much more naturally.

main 80e7b9e

This allows us to update our assert to be actual.Should().Be("1");. Naming the variable to check actual is a naming convention that helps me know exactly what I want to assert against.

main 7c75d70

We've got another subtle element of writing tests - Use tools to make it easier.

Now we need to create the variable actual and the method that'll return the value. ReSharper's option menu provides us the option to let the tool generate code for us.

Which we can fill out and extend to also include the method call we want to use

[TestMethod]
public void TestMethod1()
{
    //Given an integer of 1 should return string of 1

    string actual = FizzBuzz(1);
    actual.Should().Be("1");
}

While our tool helped us - We now can write the method call.

Arrange - Act - Assert

I use a style of test structure called Arrange-Act-Assert. Feel free to google for additional information.
This structure helps organize the test for mantainance and understanding. Things have a place, and we stick to it.
There are a couple aspects to this that'll be addressed later on; but deserve a call out here.

Act MUST only happen once

We are going to execute some code we want to assert the result of. We only do this once in a given test. More than that makes the test confusing and harder to use/understand. We don't want hard tests, we want simple tests.

There SHOULD be only one assert

We don't want a test trying to validate everything. If there's many reasons to fail, then the test isn't very informative about WHAT failed. We should have a single assert in our test.
This is a "should" and I accept aviolation of this if EVERTHING through the ACT section of a test is IDENTICAL. If everything in two tests are identical except for the assert... then sure - combine the assert section.

You may have noticed the use of "section" in the above tangent. I added some dividers to my tests to help call out the sections. Yes, even in simple tests.Yes, even in non-practice code.

[TestMethod]
public void TestMethod1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE

    //ACT
    string actual = FizzBuzz(1);

    //ASSERT
    actual.Should().Be("1");
}

main 120232f

Now that we have a defined ARRANGE section, it's disturbingly empty. Which is OK for now. We'll save that for our refactor phase.

Let's create our method

A little tool usage and we'll have some compiling code!

[TestMethod]
public void TestMethod1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE

    //ACT
    string actual = FizzBuzz(1);

    //ASSERT
    actual.Should().Be("1");
}

private string FizzBuzz(int i)
{
    throw new System.NotImplementedException();
}

With ReSharper comes their test runner, which I use because it re-runs the tests on save. Not required, but is nice. What this does is let's me know on a save if I broke something. I also don't have to remember to run the tests. Let the tool do the menial tasks.
A result of having ReSharper do this while writing this up is that I won't be including reminders or times that to run the tests - They're always running for me.

main 066eff8

And we're at a failing test!!!

That puts us onto the "2nd" step of the flow. Run all the tests and see the new one fail.

It fails! What's next? - Not so fast. Why did it fail?

We need to verify that the test failed for the reason we expected; we need our Assert to cause the failure. It's pretty obvious that this code won't fail due to the assert. We need to check.

And... It does not

Test method TddLikeYouMeanIt.UnitTest1.TestMethod1 threw exception: 
System.NotImplementedException: The method or operation is not implemented.
at TddLikeYouMeanIt.UnitTest1.FizzBuzz(Int32 i) in E:\src\github\fyzxs\MoneyExampleFizz\TddLikeYouMeanIt\UnitTest1.cs:line 25
at TddLikeYouMeanIt.UnitTest1.TestMethod1() in E:\src\github\fyzxs\MoneyExampleFizz\TddLikeYouMeanIt\UnitTest1.cs:line 17

This means we need to update the code to see the test fail for the asserted reason.

In our current state, we can make a simple adjustment to our FizzBuzz method and get to failing on the assert

private string FizzBuzz(int i)
{
    return "";
}

and now we can see the test fails for the asserted reason

Expected actual to be "1" with a length of 1, but "" has a length of 0, differs near ""

One of the things I value out of FluentAssertions is that the error messaging is easier to understand. Using the default asserting, Assert.AreEqual("1", actual) we get an output like below.

Assert.AreEqual failed. Expected:<1>. Actual:<>. 

Other more advanced asserts definitely show more informative failure messages. Informative failure messages are crucial. This is another step we want to have... but it's a tricky one.

I call it tricky because I kinda advocate breaking the rule to not make changes while we have a failing test. I think we should get the test working; but then we don't have the error message... so we kinda need to be on red to improve messaging.It's easier to take care of it right now than wait and hoepfully remember. TDD is all about fast feedback, so doing the right changes right now is the fastest way we can stay in context and fix what we want to see fixed.
I'm OK if the changes are done during refactor... but it's one of the few places that changes on RED; to specifically improve the failure message; are acceptable... to me. Your milage with your team may vary, but everyone will appreciate the improved errors.

If you have to modify code to improve the failure messge from the system for your test; that means the runtime error messaging will also be improved; and the engineer on call at 2 am on January 1st will be very grateful for the informative error message.

Looks like the next step is to make a simple change.

I don't like a simple change. It should be the simplest change.

Let's update to that

Simplest - Sounds So Simple

What is the simplest change we can make to get the test to pass?
Another way to ask this question is, "What's the simplest code to give the assert what it's looking for?"

What's the assert asking for? That's simple, we can see it right there actual.Should().Be("1");. The returned value should be "1"

We should return what the assert is comparing against.

Let's make the change!

private string FizzBuzz(int i)
{
    return "1";
}

And we're green!

main 0aa0651

What if it didn't? What if we screwed it up and it doesn't pass?

Those are great questions; and I'll "force" it into the next test so we can see some additional subtle things that we need to account for.

Currently though; Are we done?
Nope.
And we're getting sent back to the top way too quick here. What about the refactor phase?

Refactor is a very narrowly defined activity while doing TDD. It isn't "make a bunch of changes". It's small. Very narrow. Very safe operations. Our tests help us ensure things stay working, but we want to avoid big changes.

What do we consider to need cleaning by refactoring? We used 4 rules as our guide during my days as part of a Symmathesy. These are generally ascribed to Kent Beck's as his Four Rules of Simple Design.

  • Passes the tests
  • Reveals intention (should be easy to understand)
  • No duplication (DRY)
  • Fewest elements (remove anything that doesn’t serve the three previous rules)

These are really simple statements; but hold a WHOLE lot of complexity to their application. There are going to be a lot of variations of the wording, and even reducing the number of rules.

In our case; we only need to use 3 of them during the refactor phase. Our tests pass, we've already got the first rule out of the way. Keep all the rules in mind though; they help us build a robust system.

These rules are in priority order. If applying one of the lower rules negatively impacts one above, then don't change it. The most obvious is - Any change that breaks a test is not a good change. Remember; we're talking about during the refactor phase; where we should not change behavior.

These things apply to both the main code and the tests.

We will look at the main code first.

private string FizzBuzz(int i)
{
    return "1";
}

Let's apply our rules of simple design.

Reveals Intention

I've also heard ones that focus on naming; which is the part that reveals the intention... most of the time.

What do we have here that might not effectively reveal their intention?
The scope is generated for us by the tool that generated our method; and it's private. That definitely doesn't reveal the intent of this method. We will want it usable somewhere. Let's change the scope to public. It's great that we immediately stumble upon an example where it's not the name that's revealing the intetion.

public string FizzBuzz(int i)
{
    return "1";
}

What about the method name FizzBuzz?  And now... for my favorite software development joke. No idea where I got it from (but this is older so https://martinfowler.com/bliki/TwoHardThings.html)

There are two hard problems in software; Caching, Naming, and off by 1 errors.

If we consider it the game, then a better name is TakeTurn or just Turn. Maybe Transform as a more practical name. What do you think? Do you think there's a right answer?
I'm pretty sure there isn't a right answer. I've seen plentu of "makes the most sense" answers. "Right" answer though; never have. I've spent almost as much time discussing the naming of the method as I have writing the code.
I'm going to go with Transform today. I don't have the context that our method is going to be used in. That context is going to be hugely helpful in naming. For now; the behavior of the method is to transform, so that's what I'll call it.

public string Transform(int i)
{
    return "1";
}

main 00e39b9

Let's keep going with Reveals Intention. Next piece of the syntax is the parameter i. i... I don't even like i in for loops. Definitely needs to reveal it's intention.
This one is even worse than the method name. I've spent the so much time discussing what this parameter should be named that ... without a bigger context; there's not even things that make sense.

My favorite to reject is input. We know it's an input, it's a method parameter. It's not telling us the intention. number is also a fun one; because the type declaration of int tells us that. We want intention; which ... we really have no idea yet. It's not even used! We can't deduce intent yet. Which means it's easy; ignored. Since C# allows us to use an underscore _ as a name to indicate it's unused and completely ignored... _ it is.

public string Transform(int _)
{
    return "1";
}

main dce6dba

Perfect

I don't see any other intention which could be better communicated here.

Duplication? ... Nope.

Fewest Elements? ... I don't think we can get any lower.

OK - Let's go look at the test.

[TestMethod]
public void TestMethod1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE

    //ACT
    string actual = Transform(1);

    //ASSERT
    actual.Should().Be("1");
}

The name is pretty bad. That tells us nothing. Fortunately we have a comment which we used as the exact intent of the test. Can we rename it to something like that?

GivenInt1ShouldReturnString1 seems pretty doable

main 3d2d659

Since revealing intent isn't jsut about naming, but making the code infromative to the reader; I can see a couple other pieces that could be refactored to improve intent communication - The 1 as the parameter of our ACT call.

[TestMethod]
public void GivenInt1ShouldReturnString1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE

    //ACT
    string actual = Transform(1);

    //ASSERT
    actual.Should().Be("1");
}
[TestMethod]
public void GivenInt1ShouldReturnString1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE
    int valueToTransform = 1;

    //ACT
    string actual = Transform(valueToTransform);

    //ASSERT
    actual.Should().Be("1");
}

main bf8c8e9

It's a small change, but it's more informative.

Something similar can be done for the hard coded "1".

[TestMethod]
public void GivenInt1ShouldReturnString1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE
    int valueToTransform = 1;
    string transformedValue = "1";

    //ACT
    string actual = Transform(valueToTransform);

    //ASSERT
    actual.Should().Be(transformedValue);
}

main 71244d1

The tool tried to name it as expected given that's the name of the parameter in the Be method. Very helpful suggestion. And while that's right, I'm going for transformedValue to mirror the name of the other variable.

Continuing to look at naming...

public class UnitTest1

That seems pretty iffy. Let's get that renamed

public class FizzBuzzTests

main c0a5e93

Let's keep looking at our method.

[TestMethod]
public void GivenInt1ShouldReturnString1()
{
    //Given an integer of 1 should return string of 1

    //ARRANGE
    int valueToTransform = 1;
    string transformedValue = "1";

    //ACT
    string actual = Transform(valueToTransform);

    //ASSERT
    actual.Should().Be(transformedValue);
}

I don't see any intention improvement opportunities.
What about duplication? Hard to have duplication when we only have one. But a little bit.
I like Kent Beck's definition of duplication - "Code that has the same reason to change." I... can't quickly find where I get that from... but I do know I got it from him somewhere.
What do we have that might change for the same reason? Our test name and our human language intent are pretty similar. Is it DUPLICATION... questionable. There can be some argument both ways. I think it is since it is defining the intent of the test and we've updated the name to reveal the intent of the test.

If you aren't convinced; then we can invoke it under the "Fewest Elements" rule. Do we need a comment that says the same thing as our test name? No, we don't. We can totally delete it.

main 2f1f9f0

[TestMethod]
public void GivenInt1ShouldReturnString1()
{
    //ARRANGE
    int valueToTransform = 1;
    string transformedValue = "1";

    //ACT
    string actual = Transform(valueToTransform);

    //ASSERT
    actual.Should().Be(transformedValue);
}

Since I don't have other examples of duplication to be cleaned up - we'll continue on Fewest Elements.

Do we need the comments? We could remove them and have fewer elements. How would that impact the communication of the intent? We don't want to negatively impact the higher priority guides.
In this case; I think it hurts communicating the intention of the code. The clear separation and declaration of such, in a way we can't do better in just code, is going to ensure our intent stays obvious. We leave them.

Doing some digging around, I found an update to the phrasing of the 4 Rules; and I like it better.

Passes the Tests

FIRST  TEST DONE!

Our first test of fizz buzz is written! Strict TDD.  I think we're getting some solid practice in.

Show Comments