This Is The One Thing Nobody Told You About TDD
Tags: agile, webdev, tdd, softwaredevelopment
Published: 2023-05-13 05:42:46

---
title: This Is The One Thing Nobody Told You About TDD
published: true
tags: agile,webdevelopment,tdd,softwaredevelopment
canonical_url: https://fagnerbrack.com/this-is-the-one-thing-nobody-told-you-about-tdd-7f549254cd74
---

#### It's not about tests. It's about driving.

![](https://cdn-images-1.medium.com/max/1024/1*5rivTcHStoDKJEmnZFi2tA.jpeg)<figcaption>The picture of a Blacksmith at work. The blacksmith uses a hammer to hit a piece of iron on top of an anvil. The hammer hits a region that appears scalding red.</figcaption>

The tests give you hints of what to do next. They drive you to build the code. In this new chapter of the story of [“Jack, The Moneylender,”](https://dev.to/fagnerbrack_2/the-missing-practical-step-by-step-tdd-ih9-temp-slug-5508164) you’ll see an alternative way to do Outside-In Test-Driven Development **Inside** Test-Driven Development.

Imagine a blacksmith. They build the sword on fire, shape it with the hammer, and then finish it with the water.

Programming is a craft as much as blacksmithing.

However, instead of using the forge to build a sword through the power of heat, in programming you use your mind to craft the code through the power of failure. Instead of waiting to see the result after you bring the iron back from the cold, you see the result after the failure becomes a success and the test pass.

Last time I wrote the [Missing Practical Step-By-Step Test-Driven Development](https://dev.to/fagnerbrack_2/the-missing-practical-step-by-step-tdd-ih9-temp-slug-5508164). Since then, I've written how to create [a Money model](https://dev.to/fagnerbrack/how-to-build-a-money-data-type-in-javascript-4ko5-temp-slug-9291700) and how [nobody crafts the perfect code](https://dev.to/fagnerbrack_2/the-code-you-write-should-only-suck-less-than-the-one-before-4o9n-temp-slug-2035498) in the first time.

The [first post](https://dev.to/fagnerbrack_2/the-missing-practical-step-by-step-tdd-ih9-temp-slug-5508164) introduced the story of Jack, a moneylender searching for better ways to calculate the interest of his loans.

It goes like this:

> Hi, I'm Jack. My business is to give loans. I can make a small loan to you, but if you do, then I will charge interest for each dollar above a certain threshold.

> Can you build something that can do that calculation for me?

> $0 to $2000 = No Interest

> $2001 to $5000 = 9 cents per dollar

> $5001 to $10000 = 14 cents per dollar

> $10001+ = 21 cents per dollar

After [the first post](https://fagnerbrack.com/the-missing-practical-step-by-step-test-driven-development-a7140ca4b71), Jack ended up with a product that can calculate $0.09 of interest for each dollar above $2000 up to infinity. That was the first boundary of the problem. Now it's time to develop the code for the next boundary. This time, the code needs to calculate $0.14 of interest for each dollar above $5000.

Like before, there’s [a repository](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commits/v2) where you can see one commit per test run. That repository shows the Red/Green/Refactoring steps in each commit.

- The commit has a 🔴 when it represents the **red**  step.
- The commit has a green ✅ when it represents the **green**  step.
- The commit has a 🔨 when it represents the **refactoring** step.

Let’s begin.

> Jack is happy with $0.09 for each dollar above $2000, but he'll be happier if the code can calculate $0.14 for each dollar above $5000.

Let's start with a test for $5000, which is one dollar less than $5001. You should expect this test to pass and the next one to fail.

{% gist https://gist.github.com/FagnerMartinsBrack/6fe93c3e8660e8d7d2dad04e8c22ffca %}

As expected, the test pass. $5000 loan amount should still use the old rule. According to Jack's specification, the new rule of $0.14 should only apply for loan amounts of $5001 or higher.

{% gist https://gist.github.com/FagnerMartinsBrack/89eabc0022a65b7b2d7625cadda84f4a %}

Now the test for $5001 loan amount fails because it calculates $0.09 instead of $0.14. That is intentional. You haven't implemented the rule for the next range. Therefore, you can see the old algorithm still in effect. That test failure **drives** you to implement the code for the next range.

![](https://cdn-images-1.medium.com/max/894/1*wu3zG-or6IX97js7KRt1dw.png)<figcaption>The screenshot for the test failure of $5001 loan amount. It expects $270.14. Instead, it gets $270.09.</figcaption>

At this point, there are many paths to take to make this test pass. I can think of two:

1. Evolve the old conditional and find ways to write the minimum amount of code that can make this test go green as soon as possible.
2. Isolate the old conditional and create a new one to account only for the new rule. The test only passes once the algorithm is complete.

You can argue the first alternative respect the laws of TDD more than the second one. The first alternative doesn’t require you to build the whole algorithm for the test to pass, the second one probably does. Therefore, the first alternative seems more likely to assist you in achieving a good outcome. However, for the sake of experiment, let's try the second approach.

For the second approach, you can isolate the old conditional to only run for loan amounts that are greater than $2000 and less than $5001. This way you can focus solely on the code for loan amounts of $5001 or more.

{% gist https://gist.github.com/FagnerMartinsBrack/fd7e8acfbeb42717314a3f43ecc9f2f4 %}

The test failure changes from calculating $0.09 for each dollar to ignoring all calculations. Therefore, it returns $0.00. That is because you've isolated the previous logic.

![](https://cdn-images-1.medium.com/max/898/1*Qq9YogiecVHcBA2fw4kt-A.png)<figcaption>The screenshot for the test failure once you isolate the algorithm for the range between $2000 and $5000. It expects $270.14. Instead, it gets $0.00.</figcaption>

Now how would you develop the first condition for loan amounts greater than or equal to $5001? You can probably write something like "if the loan amount is greater than $5001, then start to build the algorithm for the next range."

Don't do that. You're jumping the gun!

Remember we're doing TDD. Therefore, according to the [law number 3](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd), "You are not allowed to write any more production code than is sufficient to pass the one failing unit test." The minimum condition you can write is "if the loan amount is exactly $5001, then start to build the algorithm for the next range." Anything more than that is an unnecessary level of [transformation](http://blog.cleancoder.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) to the code that hurts the purpose of what you're trying to achieve.

"Minimum" is [subjective](https://hackernoon.com/programming-subjective-discussion-9c177c31807f). You can argue that returning a static amount of $270.14 is the minimum you can write to make the test pass.

However, if you change the code like that, it won't assist you to discover the patterns that matter. You know you need an algorithm that is **similar** to the one for the $0.09 **.** Therefore, you can try to build the new requirement using the code of the previous algorithm as a template, only now you don't need to rediscover the calculation again from scratch.

If you start from the return value, the compilation errors may drive you to rebuild the old algorithm in the context of the new requirement.

Let's try.

> If you already know the solution for some parts of the problem, you can use the tests to drive you to figure out the remaining parts you don't.

The first thing is to start with the conditional for loan amounts of $5001. Create an undeclared "interest amount" variable and return that.

{% gist https://gist.github.com/FagnerMartinsBrack/9a8b63cf67f01b24904a34abb4debc82 %}

The test is still red because the code didn't declare the variable “interest amount.” However, now it fails with a different error message. The message tells you that something is missing.

![](https://cdn-images-1.medium.com/max/1024/1*tsnCeeiLcMhK7JjTYBgBuQ.png)<figcaption>The screenshot for the test failure after you create a new condition for $5001 loan amount. The text reads "Reference Error: interest amount is not defined."</figcaption>

The test is asking: **How do you calculate the "interest amount"?**

According to [the previous algorithm](https://github.com/FagnerMartinsBrack/jack-the-moneylender/blob/1182ac60360d49942916cec83b6408c162496f65/index.html#L17), the "interest amount" represents the number of cents multiplied by the dollars above a given threshold.

Let's write that.

{% gist https://gist.github.com/FagnerMartinsBrack/c96e96beee63a8630cc6a2cefc6b8c09 %}

The test still fails because the code didn’t declare the variable “dollars above threshold,” but again with a different error message.

![](https://cdn-images-1.medium.com/max/1024/1*JgvcIpfTTQxmlJ5C_K3TJw.png)<figcaption>The screenshot for the test failure after you create the variable "interest amount." The text reads "Reference Error: dollars above threshold is not defined."</figcaption>

The test is asking: **How do you calculate the dollars above the threshold?**

According to [the previous algorithm](https://github.com/FagnerMartinsBrack/jack-the-moneylender/blob/aaedff44f77628c38dff14a5a03ca258f505dc56/index.html#L16), you calculate the "dollars above threshold" as the difference between the current loan amount, which is $5001, and the value that represents the end of the previous range, which is $5000.

According to the [3rd law of TDD](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd): "you are not allowed to write any more production code than is sufficient to pass the one failing unit test." Therefore, you should not jump and write all the amounts as variables. You're in a process to discover the patterns of the code. If you jump to variables, you'll add significant [transformations](http://blog.cleancoder.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) that make it harder for you to discover those patterns.

{% gist https://gist.github.com/FagnerMartinsBrack/b31abb628e6379851ab29e1b60a02167 %}

Now here's the time for the mind-blowing twist. According to the [2nd law of TDD](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd):

> You are not allowed to write any more of a unit test than is sufficient to fail; **and compilation failures are failures.**

In this context, "Reference Errors" are considered compilation failures. Therefore, in the previous three commits, you applied a Test-Driven Development process **INSIDE** another Test-Driven Development process without using tests!

WHAT!?! Ok, I'll explain.

In the previous three commits, each compilation failure was a "Reference Error," which represented the red step of TDD. As a counterpart, the successes were the predictable changes in the error messages, the green step. The error messages drove you to [transform](http://blog.cleancoder.com/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) the code and implement part of the actual algorithm. It's similar to the red/green process of TDD, only that the predictable message changes were a replacement of a real “green” test and there was no refactoring step.

This approach is similar to [Outside In TDD](https://8thlight.com/blog/georgina-mcfadyen/2016/06/27/inside-out-tdd-vs-outside-in.html), only that instead of focusing on figuring out the connection between modules, you focus on the unknowns between the old algorithm and the new one: **the connections in your brain.**

Given you already figured out the details of how to calculate cents for each dollar in [the first post](https://dev.to/fagnerbrack_2/the-missing-practical-step-by-step-tdd-ih9-temp-slug-5508164), you don't need to spend the time to rebuild the math all over again.

> You can run a Test-Driven Development process INSIDE another Test-Driven Development process.

At this stage, the error message looks like this:

![](https://cdn-images-1.medium.com/max/1024/1*wo3zZW_mvHjPHBwAJ2xsqw.png)<figcaption>The screenshot for the test failure of $5001 loan amount after you create all missing variables. It expects $270.14. Instead, it gets $0.14.</figcaption>

The test expected $270.14 but got a result of $0.14. Remember [you isolated the previous algorithm](https://github.com/FagnerMartinsBrack/jack-the-moneylender/blob/ea640a56f0df1141f6103edd63d90019462a804f/index.html#L15) to only run for loan amounts higher than $2000 and lesser than $5001. The test tells you that you should consider the logic for the previous range too, even for the new requirement. To apply the previous logic, you can copy/paste it and concatenate with the code to calculate the interest for $5001.

{% gist https://gist.github.com/FagnerMartinsBrack/1f3e2f02c3a66619711b12c49ece6470 %}

Copy/paste? Hmm… that doesn't sound right.

You need the code for the first algorithm, but you isolated it in the first step. It seems that this is a smell of running with the second approach. The test is driving you to implement too much code, even for the smallest step necessary to make it pass.

Two years ago [I wrote](https://medium.freecodecamp.org/the-benefits-of-typing-instead-of-copying-54ed734ad849) about why copy/pasting code has a lower level of retention of knowledge than typing. Typing may force you to understand the details of the code while copy/paste may not. However, [there's no silver bullet](https://fagnerbr.com/@fagnerbrack/how-to-reject-the-belief-on-the-silver-bullet-1d86b686acbb). Every good practice has exceptions.

If **you** have written the code recently and understand all the details of it, then it's ok to duplicate. There are cases where the cost to build it again outweigh the learning benefits you'll have.

The [question](https://hackernoon.com/the-journey-for-the-right-question-c3f5b9e90035) you may ask yourself is: If I don't duplicate the code and type it all over again, am I going to learn something new that I didn't know before?

> It's ok to duplicate the code as long as the cost to build it again outweigh the learning benefits you’ll have.

Unfortunately, the last change still didn't make the test green.

![](https://cdn-images-1.medium.com/max/1024/1*8z-A8JulOde0LqrYznGthw.png)<figcaption>The screenshot for the test failure after the copy/paste of the previous algorithm. It expects $270.14. Instead, it gets $270.23.</figcaption>

The test is still trying to tell you something.

Given you copy/pasted the previous algorithm, the code is trying to calculate $0.09 plus $0.14 for the $1 above the threshold of $5000. The result is $0.23. The fix is to make sure that $5001 only adds up the remaining cents between $0.14 and $0.09, not the sum of them.

{% gist https://gist.github.com/FagnerMartinsBrack/221c832a985c9bfa2ab08e7532ef6800 %}

Here it is, the test is green!

You've got the logic for $5001 loan amount correct. Now it's an excellent opportunity to try the [Rule Of Threes](https://fagnerbrack.com/how-to-solve-the-challenge-of-reusability-ac4edce069c9) and see if you can repeat the same pattern for $5002 and $5003 loan amounts.

{% gist https://gist.github.com/FagnerMartinsBrack/e6adee6c44ade95f6cdb555bb8afc9b5 %}

Once you do that, you'll see that every time you duplicate the code, you only need to change two values to make the next test pass.

Only now you have [empirical evidence](https://en.wikipedia.org/wiki/Empirical_evidence) that those values should become variables.

{% gist https://gist.github.com/FagnerMartinsBrack/9ec10daf1e705e40e9bd5091c1a1f681 %}

Congratulations! You've found the code pattern for the next range.

The next steps can be a series of refactorings to clear up the [duplication](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commit/18e7337129f85efcd8fbf5632c0f47b6eae1b546), [remove all Magic Numbers](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commit/f30fe0ebd446fc2ff32a2eb22584ea0212fa9c08) and separate the new tests in a [new block](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commit/d5cdbc7eb618127aa0bd5546e11b7bbe2d0adc44).

The result is a code that adds up $0.09 for every dollar above $2000 and $0.14 for every dollar above $5000. You get a solution that delivers [incremental value](https://dev.to/treyhuffine/code-less-think-more-incrementally-208m-temp-slug-8196626) exactly as what [the first post](https://dev.to/fagnerbrack_2/the-missing-practical-step-by-step-tdd-ih9-temp-slug-5508164) did.

> The purpose of TDD is not to create tests. It's to serve as a foundation that can assist you to discover patterns in the code and understand better the [problem](https://dev.to/treyhuffine/the-problem-you-solve-is-more-important-than-the-code-you-write-2b0g-temp-slug-2836748) you're trying to solve.

There's one more thing.

The tests also serve as a documentation of your code, [better than code comments](https://fagnerbrack.com/code-comment-is-a-smell-4e8d78b0415b). Therefore, it makes sense to use the language of the domain instead of the language of the programmer for the description of the tests. This way, if a new programmer comes to the project, they can easily understand the problem and the code because they have clear examples. The code won't miss critical information of the [problem it's trying to solve](https://dev.to/treyhuffine/the-problem-you-solve-is-more-important-than-the-code-you-write-2b0g-temp-slug-2836748).

{% gist https://gist.github.com/FagnerMartinsBrack/daecbb446ab1b363e096ba01b08fd21d %}

Also, you can review the code coverage and adjust accordingly. The value of TDD is not to add 100% code coverage or merely to create tests. It's to have a [discipline](https://hackernoon.com/how-to-fix-the-software-industry-c2b627ec3d9d) where you can ask the [right questions](https://hackernoon.com/the-journey-for-the-right-question-c3f5b9e90035) to provide empirical evidence the code you write understands the boundaries of the problem [it's trying to solve](https://dev.to/treyhuffine/the-problem-you-solve-is-more-important-than-the-code-you-write-2b0g-temp-slug-2836748).

> TDD is about driving you into enlightenment.

Similar tests are useful to help you to find the patterns of the problem. Once the patterns are clear, you're free to lock down the tests into boundaries that are more likely to cause regressions or boundaries that can provide to the future reader of the code better examples of how the system works. In the case of the interest rates for Jack, the boundaries of the problem are the changes of interest rates between $0.00 to $0.09 per dollar and $0.09 to $0.14 per dollar.

{% gist https://gist.github.com/FagnerMartinsBrack/1b52890c7312a4cb54cebcbf1b6e14ce %}

Jack is happier, for that he can now calculate loan amounts above $5000. Now you can go home confident that your work created real value. You can be confident this knowledge affords you to work less and do more.

In a next post, I’ll evolve this code to test for the subsequent boundaries. You’ll see how you can apply the same [techniques](https://fagnerbrack.com/the-trick-to-write-better-software-lies-on-the-technique-944015f84ce4) to calculate loan amounts with $0.21 of interest rate for every dollar above $10000. Also, I'll refactor the code to remove the duplication.

At that point, perhaps you’ll get to the end of this story.

Stay tuned 📻.

Thanks to [Jay Bazuzi](http://jay.bazuzi.com/), [Siddharth Salunke](https://www.linkedin.com/in/siddharthsalunke/), [Martin Pain](https://www.linkedin.com/in/martin-pain-10454329/), [Paulo Clavijo](https://twitter.com/pclavijo) **,** [Chris Stenke](https://twitter.com/chrizzz0101) and [Dave Schinkel](https://twitter.com/DaveSchinkel) for their insightful inputs to this post.

* * *