The Missing Practical Step-By-Step TDD
Tags: webdev, tdd, programming, softwaredevelopment
Published: 2023-05-13 05:37:42

---
title: The Missing Practical Step-By-Step TDD
published: true
tags: webdevelopment,tdd,programming,softwaredevelopment
canonical_url: https://fagnerbrack.com/the-missing-practical-step-by-step-test-driven-development-a7140ca4b71
---

### The Missing Practical Step-By-Step TDD 🔊

#### So you have a fixed specification and deadline? Sorry, that's not an excuse

![](https://cdn-images-1.medium.com/max/1024/1*dNZiRVzIJ84mtadcWwzMZA.jpeg)<figcaption>A man with a shaved beard. He’s counting some money in a table.</figcaption>

<iframe src="https://cdn.embedly.com/widgets/media.html?url=https%3A%2F%2Fplay.ht%2Farticles%2Fa7140ca4b71&amp;src=https%3A%2F%2Fplay.ht%2Fembed%2F%3Farticle_url%3Dhttps%3A%2F%2Fmedium.com%2F_p%2Fthe-missing-practical-step-by-step-test-driven-development-a7140ca4b71&amp;type=text%2Fhtml&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;schema=play" width="700" height="185" frameborder="0" scrolling="no"><a href="https://medium.com/media/bab1564e2b8f4aac96ab3c498a2fff65/href">https://medium.com/media/bab1564e2b8f4aac96ab3c498a2fff65/href</a></iframe>

A lot of people say [TDD is useful](https://medium.freecodecamp.org/why-test-driven-development-4fb92d56487c). However, it's not rare to encounter a developer who says they have been doing TDD for a long time, only to find out they have been doing something completely different.

It's like [Wittgenstein's Beetle](https://medium.com/@fagnerbrack/wittgenstein-s-beetle-in-software-engineering-dcea89a5db92). If everyone has a black box and they call the content of that box a "beetle," then it's normal for two people to talk about the same thing in general terms, like "TDD is good" or "TDD is bad." However, it's also very likely each one of them has a different understanding of the subject they're talking about in concern to the other person's thoughts.

This is a post I wrote as an attempt to show, in practice, better ways to find the solution to a problem. It's a way to make sure you understand what TDD means, or at least what it means to me.

I'll try to follow [the three great rules](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd):

> 1. You are not allowed to write any production code unless it is to make a failing unit test pass.  
> 2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are failures.  
> 3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.

> Test-Driven Development is [subjective](https://hackernoon.com/programming-subjective-discussion-9c177c31807f). Each person has a different understanding of how to apply it.

Alright, so what's the [problem we're going to solve](https://dev.to/treyhuffine/the-problem-you-solve-is-more-important-than-the-code-you-write-2b0g-temp-slug-2836748)?

Let's ask the infamous Jack:

> Hi nerd, I'm Jack, The Moneylender.

> My business is to give loans. For example, I can make a small loan to you but don't try to get too much. If you do, then I will charge interest for each dollar above a certain threshold.

> However, it's hard for me to calculate that. Build something that can do it for me, alright? That's what you're paid for, right?

> Oh, an I almost forgot: **I need that for Friday**.

> $0 to $2000 = No Interest

> $2001 to $5000 = 9 cents per dollar

> $5001 to $10000 = 14 cents per dollar

> $10001+ = 21 cents per dollar

This story resembles very well to small problems you might encounter in real life, even the "Friday thing."

Given the characteristics of the problem for "Jack, The Moneylender," this post is going to use the London school of TDD, also known as [Inside Out Test-Driven Development](https://8thlight.com/blog/georgina-mcfadyen/2016/06/27/inside-out-tdd-vs-outside-in.html).

There's [a repository](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commits/v1) where you can see one commit per test run. That repository shows the Red/Green/Refactoring steps.

- 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.

This post follows the same principle as [Code Review and Test-First Development](https://medium.com/@fagnerbrack/code-review-and-test-driven-development-4c19b69b5761). The difference is that I've split the commits to expose my thoughts around Test-Driven Development, not Test-First.

> Jack is a [Moneylender](https://en.wikipedia.org/wiki/Moneylender), and he's also an a##hole.

First thing is to set up the coding environment with a green test to make sure all dependencies are working fine. My preference is to make something simple, without npm or [any other dependency](https://medium.freecodecamp.org/ok-now-ill-learn-how-to-program-in-javascript-2c7847414830) besides the test library.

Let's get back to our roots and start it with [QUnit](https://qunitjs.com):

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/25278692e62182d577500c6ad3a543c2/href">https://medium.com/media/25278692e62182d577500c6ad3a543c2/href</a></iframe>

In the assertions, I want to avoid the [Parameter Trap](https://youtu.be/loj3CLHovt0?t=22m25s) from QUnit's API which exists on the "actual" and "expected" arguments. Therefore, I create another function with named arguments that can receive QUnit's "assert" object.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/9be1b1776fdce4fca836f8c241d80398/href">https://medium.com/media/9be1b1776fdce4fca836f8c241d80398/href</a></iframe><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/8096d1c70287e1f952cc6a8323e49eb8/href">https://medium.com/media/8096d1c70287e1f952cc6a8323e49eb8/href</a></iframe>

Now let's look at the problem. There are some critical boundaries for this: 0 to 2k, 2k to 5k, 5k to 10k and 10k+.

Let's start with something simple. I'll focus to have only one assertion that may drive me to increase the minimum level of [transformation](https://8thlight.com/blog/uncle-bob/2013/05/27/TheTransformationPriorityPremise.html) of the code, nothing else.

The most straightforward test I can think of is a loan amount of zero dollars. However, that doesn't seem like a reasonable business case. If Jack wants to calculate a loan amount of zero, that means he's not giving a loan to anybody. Therefore, he won't see value in the code I write.

Just thinking about the first test drove me to ask Jack about this particular case:

> A Loan of zero? Come on, that's ridiculous!

> Oh, wait, I did send you zero dollars in the specification… ok.

> Glad to see you've uncovered a flaw in my logic. I didn't know you were capable of doing that!

> — Jack, the ass… err… The Moneylender

I initialize the variable “interest to pay” as “undefined” to have a default “empty” result and a test failure. The expected result, assuming that Jack is giving a loan of $1, is zero.

Therefore, the test fails, because the variable is "undefined."

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5b5cce7005e4d262135de9e354502543/href">https://medium.com/media/5b5cce7005e4d262135de9e354502543/href</a></iframe>

For the purpose of this post, let's imagine the **server** represents the code that produces something consumable for the client. The **client** consumes whatever the server produces.

In the context of the test above, the **server** is something that calculates the result for variable "interest to pay." The server is not explicitly modeled at this stage. The **client** is the test code. The test code reads the variable "interest to pay" and pass it to the assertion function.

The most straightforward change I can make in the **server** that can drive me to make the test to pass is to return $0 to the variable "interest to pay."

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cfb10e9c1b625498bb9d6640bee4b7c9/href">https://medium.com/media/cfb10e9c1b625498bb9d6640bee4b7c9/href</a></iframe>

For the next test, let's code the behavior for a loan amount of $2. Given I've not modeled the **server** before, now it's an excellent opportunity to do it. The next test assumes a function with the name "interest to pay for dollars."

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/d800d32d3b03c6ccb9eca28cacebf249/href">https://medium.com/media/d800d32d3b03c6ccb9eca28cacebf249/href</a></iframe>

Oh, the test throws a "Reference Error" because the function doesn't exist, but that's ok.

The [rule number 2](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd) of TDD clearly says:

> […]; and compilation failures are failures

> — [Rule of TDD number 2](http://butunclebob.com/ArticleS.UncleBob.TheThreeRulesOfTdd)

In this context, you can read the rules as:

> […]; and reference errors are failures

![](https://cdn-images-1.medium.com/max/1024/1*8Ll0v9Uar5vM22Fk_5_yEg.jpeg)<figcaption>The screenshot for the QUnit test failure. It fails with a Reference Error; the message is that the function "interest to pay for dollars" is not defined.</figcaption>

Alright, so I assume that error is a regular test failure. However, a "fix" in this context is not going to result in a passing test, it's going to result in a different error message.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/2254df0ee732fd2abbce2257a0d0bbe7/href">https://medium.com/media/2254df0ee732fd2abbce2257a0d0bbe7/href</a></iframe>

Now the failure comes from the QUnit assertion. It looks like I've fixed it.

![](https://cdn-images-1.medium.com/max/1024/1*L5sHZWftKJ1yKk0hrtob7w.jpeg)<figcaption>The same screenshot as before, only now it shows an assertion failure. It reads "expected: 0", "result: undefined."</figcaption>

If the user asks for a loan of $2, the interest is going to be zero..

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/7820c65e538353e49c68a0d7e84c08b8/href">https://medium.com/media/7820c65e538353e49c68a0d7e84c08b8/href</a></iframe>

If I keep incrementing one dollar over and over again, there's nothing that can drive me to change the code to make it more generic. A new test that follows this pattern gives me no value whatsoever. It will always pass because every loan amount that returns no interest will be valid until I start to test the next boundary of $2001 or more.

It's time to change the strategy.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/e924e7c10f7c02934293267315c0663f/href">https://medium.com/media/e924e7c10f7c02934293267315c0663f/href</a></iframe>

However, although a new passing test doesn't provide value to my Test-Driven approach, it helps me to understand whether it makes sense to [DRY](https://en.wikipedia.org/wiki/Don%27t_repeat_yourself).

Notice I've duplicated the code for the third time. Therefore, [as I wrote in the past](https://medium.com/@fagnerbrack/how-to-solve-the-challenge-of-reusability-ac4edce069c9), there's enough evidence that I can refactor the code into a reusable function.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f78688a8c833c06bbdf114bdfcf4e5d8/href">https://medium.com/media/f78688a8c833c06bbdf114bdfcf4e5d8/href</a></iframe>

There's one thing I find very helpful when testing the boundaries of a problem, which is to test the surroundings at least three times. In the context of "Jack, The Moneylender" problem, the surroundings are the loan amounts of $2000, $2001 and $2002.

**_Note:_** _For the sake of brevity, I'll stop referring to the red and green tests separately. However, assume there's always a failing test for every snippet. If you go to the_ [_Github project_](https://github.com/FagnerMartinsBrack/jack-the-moneylender/commits/master)_, you can see the passing and failing test in each commit separately._

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/ccec2a01d4b395252a0f0dda09c36979/href">https://medium.com/media/ccec2a01d4b395252a0f0dda09c36979/href</a></iframe>

The $2000 loan amount doesn't help much, the test just passes because it's in the same range as before. I still expect it to be zero.

Let's try $2001. It should not return zero, but 9 cents.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/03262197f64bf9f333f6a3989c625585/href">https://medium.com/media/03262197f64bf9f333f6a3989c625585/href</a></iframe>

Here you can see a widespread software development mistake: the use of floating point numbers to represent currency. There is [a lot](http://wiki.c2.com/?FloatingPointCurrency) of [content](https://husobee.github.io/money/float/2016/09/23/never-use-floats-for-currency.html) out [there](https://stackoverflow.com/questions/3730019/why-not-use-double-or-float-to-represent-currency) about [this subject](https://spin.atomicobject.com/2014/08/14/currency-rounding-errors/). Therefore, I won't get into the details here.

To understand the precision impact my code would have, I created a JSFiddle to simulate the dollars between $2001 and $5000:

<iframe src="https://cdn.embedly.com/widgets/media.html?src=https%3A%2F%2Fjsfiddle.net%2Ffagnerbrack%2Frzdnc26o%2Fembedded%2F&amp;url=https%3A%2F%2Fjsfiddle.net%2Ffagnerbrack%2Frzdnc26o%2F&amp;image=https%3A%2F%2Fwww.gravatar.com%2Favatar%2F0bc8a8a53f215ebcbdc39479f182c803%3Fs%3D80&amp;key=a19fcc184b9711e1b4764040d3dc5c07&amp;type=text%2Fhtml&amp;schema=jsfiddle" width="600" height="400" frameborder="0" scrolling="no"><a href="https://medium.com/media/76331b861aa0ca93b11e85c49b166054/href">https://medium.com/media/76331b861aa0ca93b11e85c49b166054/href</a></iframe>

**_Note:_** _To avoid going on tangents and deviate from the main subject of this post which is TDD, I'll use the JavaScript "Number" type as if it could represent a currency correctly. I'll show how to convert this into a testable model,_ [_as I've written in the past_](https://medium.com/@fagnerbrack/the-test-should-verify-if-the-code-solves-the-problem-not-if-it-runs-afea37a3a6e)_, in another post. Stay tuned._

If we continue the path we were going, we'll eventually have a production code that looks like this:

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5e2e8a450d9f992d9ce8006f0992325a/href">https://medium.com/media/5e2e8a450d9f992d9ce8006f0992325a/href</a></iframe>

I can see a lot of [Bad Code Smells](https://medium.com/@fagnerbrack/code-smell-92ebb99a62d0) in the code. The most annoying one seems to be the duplication that is a result of the sum of the constants that represent the 9 cents. It makes sense to refactor the code to use multiplication.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/67b3b8f7c4a93edc8ecbec2561aea180/href">https://medium.com/media/67b3b8f7c4a93edc8ecbec2561aea180/href</a></iframe>

Now I can see a pattern. Although writing dumb conditionals seems naive at first, they are showing to me that for every dollar above $2000 I can multiply that amount by 9 cents. Of course, until I reach the next range of $5001.

Also, I can change the [Magic Number](http://wiki.c2.com/?MagicNumber) that represents the right-hand multiplication factor. If I put the "loan amount" minus the last value of the previous range, I'll always get an incremental result for each value inside the conditionals.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/47bb77499f7e5d5254376a4acdf07242/href">https://medium.com/media/47bb77499f7e5d5254376a4acdf07242/href</a></iframe>

Now I’ve spotted a great opportunity to [think incrementally](https://dev.to/treyhuffine/code-less-think-more-incrementally-208m-temp-slug-8196626)!.

The requirements say I need to support a lot of ranges. However, if Jack is ok with it, he can have a working software **right now**. He can paste the function in Chrome DevTools and see if the loan amount he's giving has “no interest” or how much he should charge on the basis of 9 cents per dollar. For a loan bigger than $2001, he needs to wait until I can support the other ranges.

I asked Jack, and he’s impressed. He didn’t even know such a thing was possible! He thought that he would have to wait a lot of time to get the software with all the requirements done. Most of his customers get a loan that is around the $2000 range, so that approach has big value to him!

To produce valuable software, I need to remove all other conditionals and only leave the first one. Also, I need to change the equality comparison to "greater than or equal."

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/3a851e5c788c801e21a287de50b31988/href">https://medium.com/media/3a851e5c788c801e21a287de50b31988/href</a></iframe>

Next step could be to add descriptions for each boundary of the problem in the tests.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/4a7b9f2f67d2beaa433bdf8f10fa91b0/href">https://medium.com/media/4a7b9f2f67d2beaa433bdf8f10fa91b0/href</a></iframe>

It's not done yet.

I need to remove all remaining [Magic Numbers](http://wiki.c2.com/?MagicNumber). After all, who ships code with constants that are meaningless to the next developer? I don't.

<iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/cd149ae3e4f379b17820f58678ce6d85/href">https://medium.com/media/cd149ae3e4f379b17820f58678ce6d85/href</a></iframe>

Once that’s done, then I can say I have a deliverable software; only it doesn't have all the features baked in. If I want to, I can code a dumb web server that just prints out the result as HTML and use the function in the back-end.

> If you have a fixed specification and a set deadline, you have to think outside that black box

I've been given a problem with a fixed specification and a set deadline. TDD has helped me to figure out flaws in the original specification. Also, by merging the [incremental thought](https://dev.to/treyhuffine/code-less-think-more-incrementally-208m-temp-slug-8196626) with the process of TDD and [focusing on the problem](https://dev.to/treyhuffine/the-problem-you-solve-is-more-important-than-the-code-you-write-2b0g-temp-slug-2836748) at hand, I managed to cut down delivery time. I wouldn't be able to do that if I had started coding everything in one go.

A Big bang mindset.

Maybe the end result is not the [**best** solution](https://hackernoon.com/how-to-destroy-the-myth-of-the-proper-solution-94ca958def1f). Test-Driven Development is not about being perfect, it's about writing code so that the tests can drive you to **some** solution. It helps you to build up a better understanding of the problem. It doesn't turn you magically into a great coder, problem solver or software designer.

Also, TDD can serve as a discipline to help document your thoughts in the tests and write code at the same time. That's very useful for [Pair Programming](https://medium.com/@fagnerbrack/pair-programming-8cfbf2dc4d00) and [Mob Programming](https://hackernoon.com/how-mob-programming-will-make-you-more-effective-590a1b7e0418), where nobody knows the details of the beetle each other's head.

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://medium.com/@fagnerbrack/the-trick-to-write-better-software-lies-on-the-technique-944015f84ce4) to calculate loan amounts with different interest rates. Also, I'll make some changes to the test descriptions so that they use the language of the domain, not the language of the developer.

Stay tuned 📻.

Thanks for reading. If you have some feedback, reach out to me on [Twitter](https://twitter.com/FagnerBrack), [Facebook](https://www.facebook.com/fagner.brack) or [Github](http://github.com/FagnerMartinsBrack).

Thanks to [Jon Eaves](https://joneaves.wordpress.com/), [Jay Bazuzi](http://jay.bazuzi.com/), [Riccardo Odone](https://twitter.com/RiccardoOdone), [Siddharth Salunke](https://www.linkedin.com/in/siddharthsalunke/), **Stephan Classen** and **Leonti Bielski** for their insightful inputs to this post.