The origin, the what, and the how of TDD (by building a FizzBuzz Flutter/Dart feature)
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.
THE ORIGIN:
It’s the 1970’s and you’re sitting in front of one of these bad boys.
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”.
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.
“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
Once the file is created, write comments based on our assumptions/ requirements.
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’
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.
Let’s run the test file again and now the test output says that there are no tests
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 FizzBuzzSolver
and 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.
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
Once the class is created, come back to the test file and import it
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.
Now we can run the test again, and this time the test will fail. It expected the String “Fizz” but got null instead.
So let’s go to the solve method and do the minimum required work.
Now if we rerun the test, the test will pass!
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
.
Now run the test again,
the first test will pass but the second test will fail because it expected Buzz but got Fizz.
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
Now let’s run the test again and this time it should pass.
At this point, we can go back to the solver
class and refactor our code to make it more readable.
In the test file, I will add another test case with input 6, which will then fail.
I will then refactor the solver
to make the test pass.
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.
Running it on the command line will fail the test, and then we can refactor the solver to make the solution more generic.
Once done, re-run to make sure that the test cases pass.
Now I will write a test case with input 15 and expect 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.
After each code change, run the test again to make sure everything works.
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.
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.
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