The origin, the what, and the how of TDD (by building a FizzBuzz Flutter/Dart feature)

Rawaha Muhammad
Flutter Community
Published in
10 min readAug 13, 2024

Let’s walk through the journey of time to understand TDD and how to apply it using Flutter/Dart.

Prefer video format instead of reading? I got you covered.

This article in video format

THE ORIGIN:

It’s the 1970’s and you’re sitting in front of one of these bad boys.

Left: A typewriter that outputs instructions on a paper tape, called ASR-33. Right: A computer that takes the paper tape as input to execute instructions, called Harwell Dekatron WHICH 10.

Your goal here is to write a program on this typewriter (left) which will then output instructions into the paper tape and then fed into a shiny computer (right) to generate your desired output.

But the question is how do you write a program so that you don’t spend a lot of time going back and forth and using all your paper tape?

So you come up with an efficient approach that involves testing a bunch of inputs and their outputs and then writing the program for it. This not only reduces the amount of adjustments you have to make but also, you finish the program much quicker.

Excited about this approach, you tell your peers and they say “Congratulations dummy, we’ve been doing this for years”.

A GIF relating to what your peers might say to you

TDD stands for test-driven development. Although the term was popularized by Kent Beck in the early 2000s, the idea of “test first development” had always been there for a long time.

We know that the programmers on the Mercury Space Capsule wrote their unit tests in the morning and made them pass in the afternoon, a kind of foreshadowing of test-driven development — Uncle Bob in “Uncle” Bob Martin — “The Future of Programming”

That’s why Kent Beck calls it a “rediscovery” and a little internet search in your favorite browser (I hope it’s Arc), will tell you that the Mercury space capsule, AKA Project Mercury, started in 1958 and concluded in 1963.

Fast forward to 2024 which is the age of AI solutions and self-driving cars, the concept of TDD has been long lost in junior developers and nobody knows exactly what TDD is.

In this article, I will try my best to give you an understanding of what TDD is, why Kent Beck and Uncle Bob advocate for it, how to apply it, and why you should use it in your projects.

WHAT IS TDD?

Test Driven Development is a testing, designing, documenting, and anxiety-reducing technique that revolves around testing first while building software.

It builds on creating an incremental feedback loop which gives you logical insights about the feature you’re building. So you learn where you’re going wrong when you fail the test, fix the feature based on the learnings from the failed test, and refactor if need be. You keep doing this until you reach the final solution.

A picture of the incremental feedback loop TDD creates that we can use to solve errors and progress towards the solution by failing and eventually, passing the unit test

“But why tests first? That is no fun!”

Well, writing tests afterward when the code breaks is much less fun. Even if the code doesn’t break we’re just writing tests afterward to fulfill a formality and satisfy ourselves but what we’re saying is that “the code that I wrote is the code that I wrote”. More often, the test written afterward does not verify the behavior of the feature at all.

I believe that TDD has 4 central parts (That’s 100% my opinion, but don’t be afraid to take it out in the comments section)

  • You need to make a list of assumptions/requirements for the feature you are about to build
  • Red means to pick a test from those requirements and fail it intentionally
  • Green means to pass that test using the minimum required changes
  • and the last part is refactoring that code so that it is clean and the tests still pass

then write another test and repeat the process until all your requirements from the list are met.

The goal of TDD is to make you fall in love with failing tests and never be afraid of their errors. Instead, use the failing test as constructive feedback and implement it in your code to make the test pass.

HOW TO IMPLEMENT TDD?

We’re going to be building a Fizz Buzz solver using TDD.

At this point, if you don’t know anything about unit testing, I would recommend you go to this article and have a refresher on what unit testing is first.

FizzBuzz is a small program that takes a number as input and returns it as a String. But the twist is that it returns “Fizz” if the number is a multiple of 3, and “Buzz” if it’s a multiple of 5. If the number is both a multiple of 3 and 5, then the program should return “FizzBuzz”.

To build a FizzBuzz feature we can quickly extract some specifications from the above description:

  • Takes number as input and returns it as a String
  • Returns “Fizz” if the number is a multiple of 3
  • Returns “Buzz” if the number is a multiple of 5
  • Returns “FizzBuzz” if the number is a multiple of both 3 and 5

These specifications will now become our requirements for the feature, and we will write unit tests for them one by one until all requirements are met.

Now go ahead and fire up your favorite editor and create a flutter project. go to the test directory and create a file called fizzbuzz_test.dart

Image showing fizzbuzz_test.dart file created inside the test directory

Once the file is created, write comments based on our assumptions/ requirements.

Image showing comments written inside the fizzbuzz_test.dart file

Go to the terminal and run the flutter test test/fizzbuzz_test.dart

You ran the first test and it failed, now you need to consider the failed test feedback and write the solution to make the test pass.

In this case, we can see that the Error says, Undefined name ‘main’

Image showing “Error: Undefined name ‘main’” when flutter test command is run

That means that we need to define a main method in our test fiue, so let’s go ahead and do that. Keep in mind that you need to do the minimum amount of work to make the test pass.

Image showing the main method added to the test file

Let’s run the test file again and now the test output says that there are no tests

Image showing flutter test output as No tests ran and No tests were found

So let’s create our first test.

We will pick a requirement we commented in the test file and then write a test case for it.
The first requirement is that the program should return “Fizz” when a multiple of 3 is entered.
This will become our test case description. The only difference is that we have added the number 3 to the description to make it more clear

When writing the unit test, the first thought that comes to mind is that we need some sort of a Class that will be responsible for handling the logic, let’s call it FizzBuzzSolverand we know the the input to our function would be 3 so let’s declare that as well.

After that, we need a function that takes input and returns the output

finally, we can expect the output to be equal to Fizz, since the input number is a multiple of 3.

Image showing the first test case written

There is a red line beneath our class which means that this class is not defined, so let’s create this class in the lib folder

Image showing file “fizzbuzz_solver.dart” created and FizzBuzzSolver class created inside it

Once the class is created, come back to the test file and import it

Image showing the FizzBuzzSolver class imported inside the test file

Now we got another error on the solve function which means that this is also not defined, so we will create this function as well.

I am doing the minimum amount of work, because my sole purpose is to pass the test, not to complete the feature.

Image showing the solve function created inside the FizzBuzzSolver class

Now we can run the test again, and this time the test will fail. It expected the String “Fizz” but got null instead.

Image showing test failed

So let’s go to the solve method and do the minimum required work.

Image showing “Fizz” string returned from the solve function

Now if we rerun the test, the test will pass!

Image showing the test passed

At this point, you must be shouting “This is the wrong solution”

But what you should be thinking about is

“What test can I write to prove that this is the wrong solution?”

Using our previous knowledge, we can deduce that if we write a test for input 5, we can prove that the current solution is wrong.

To have the solver instance as global, we are going to move it out of the first test’s body and into the main function, we will initialize it inside the setUp function which will run every time before a test runs, so every test will get a fresh instance of FizzBuzzSolver.

Image showing refactor of the solver instance to the setUp function and addition of test case as input 5

Now run the test again,

the first test will pass but the second test will fail because it expected Buzz but got Fizz.

Image showing the second test failed

So let’s go ahead and modify the solution

I went to the solver file and wrote if the input is 3 return Fizz, otherwise return Buzz

Image showing the logic entered in the solve method as explained above

Now let’s run the test again and this time it should pass.

Image showing both tests passed

At this point, we can go back to the solver class and refactor our code to make it more readable.

Image showing refactor of solve method to make it more readable

In the test file, I will add another test case with input 6, which will then fail.

Image showing the test case with input 6

I will then refactor the solver to make the test pass.

Image showing the introduction of the modulus operator to handle cases of multiples of 3

What I am doing here is one of the rules of TDD

To write specific test cases that will make the code more generic.

Let’s write another test case, this time with 10 as input and expecting Buzz as output.

Image showing test case with input 10 added to the test file

Running it on the command line will fail the test, and then we can refactor the solver to make the solution more generic.

Image showing using the modulus operator to handle cases of multiples of 5

Once done, re-run to make sure that the test cases pass.

Image showing all the test cases passed

Now I will write a test case with input 15 and expect FizzBuzz as output.

Image showing the addition of a new test case with input 15, expecting FizzBuzz as output

Running the test will disprove my current solution and this way I will be forced to write a better solution.

So let’s go back to the solver and handle the case where input is both a multiple of 3 and 5.

Image showing handling the case if the number is both a multiple of 3 and 5 inside the solve method

After each code change, run the test again to make sure everything works.

Image showing all 5 test cases passing

Let’s refactor the modulus code into a new function called _isMultipleOf, it will simply take two integers as input and return true if the number is a multiple. We can reuse this in our code and make our condition statements look neat.

Image showing refactoring of the solve method to make it look more readable, by introducing a new private method called _isMultipleOf

Till now, 80% of the work is done, and I know that you can do the rest of 20% yourself. I’ll leave the complete solution on my Github, so if you get stuck, you can take a look at that. (NO CHEATING)

Now let’s come back to the beautiful code we’ve just created. (Refer to the image above)

TDD helped us write way better code.

It’s safe to say that this is a far better solution than what we would have written without TDD.
This technique not only forced us to write the correct solution, but we also applied some software patterns implicitly.

Image showing the implicit benefits of TDD by visualizing the software patterns applied

TDD not only documented our code through test descriptions, but it also increased our confidence in the code we wrote.

Great, I can finally use TDD at work

Not really…TDD is a high-value skill that can be acquired by working hard and NOT by only applying it at work.
If you take TDD to work, it might be the worst week for you and it will slow down your productivity by at least a multiple of 10 (Buzz). So the next time your manager wants you to use TDD, show them this article.

So learn it well, get good at it, know that you’re good at it, and then bring it to work.

For learning purposes, check out this amazing website I found for practicing TDD, you already completed the first exercise so it should be no problem getting started.

Thanks for reading and I hope you enjoyed the article.

If you liked the tutorial…

Please follow me here and on my socials!

Youtube: https://www.youtube.com/@runtimesnippets
LinkedIn: https://www.linkedin.com/in/rawahamuhammad/
Github: https://github.com/coffiie
Medium: Rawaha Muhammad

--

--