Part 2 | FizzBuzz TDD
In this part of the FizzBuzz series I will go over my experience of doing a test-driven development of FizzBuzz.
Following from Part 1 of FizzBuzz which I wrote in plain English, I now want to move onto writing the actual program that will meet the following conditions I have set out. I will be following the Test Driven Development (TDD) process to help me in writing executable and refactored code.
Here is what TDD is in my own words:
-
Write a test case before the actual code as this drives towards writing better code or code that you actually need
-
Run the tests and see it fail since you haven’t implemented the code yet but this shows that the test is working
-
Then write the code and implement it
-
The tests should render successful if the code has met all the conditions/requirements
-
The code must be cleaned up (refactored) throughout testing and implementation stage to make sure it is readable and it is not repeated - the test made before is there to check that the code is still working even after refactoring
As I will be writing the code in pure Ruby, I will be using RSpec, the testing tool for Ruby.
Setting Up
First the RSpec gem has to be installed:
Then make a new project directory with lib, spec directories which should have the program and spec file inside respectively.
Alternatively you can also do the following command to set up the spec directory:
Create a Gemfile with the following:
Then run the following command in the project directory to install the rspec gem and all its dependencies:
In the fizzbuzz_spec.rb file we want to input the following code:
Then when we run the RSpec command which checks our tests with corresponding code/program/method:
The error will be as follows:
This is because we do not yet have a class method called FizzBuzz yet. In fizzbuzz.rb file we need to input:
Require this in our fizzbuzz_spec.rb file:
Now when we run RSpec, we get:
This means that the files are now linked and we can start doing our first test.
Extra notes: You can also add the spec_helper.rb file (or it has already been added when you did rspec init command) in your spec directory to configure your command line output:
This will include colours in your terminal. There are other configurations which can be found at RSpec.
Testing and Coding
The first test I will be writing is to meet the conditions of printing out Fizz when a number is divisible by 3:
Running Rspec:
You can see that divisible_by_three method is not defined yet. Therefore we go back into our fizzbuzz.rb file to define this method:
Running RSpec:
Now RSpec tells us that the expected was “Fizz” however we actually got nil. This is because our method did not return anything. We can now go on to include Fizz into our method:
Running RSpec:
This means our test has now passed!
But of course we know that we don’t just simply want any number to be “Fizz” and it is just for numbers that are divisible by 3. As the test is working up to this point, we can go ahead to write the method and see whether the test is still passing:
The test has passed! Hooray!
Now we can go ahead to write up the tests for our other conditions:
Running RSpec
As expected, the three new methods we tested are not yet defined in our code. We can go ahead and write them up in our fizzbuzz.rb file:
You should usually do the following steps based on failed tests:
-
Undefined method - go and define the method
-
Method returned “nil” - input what you expected in your test
-
Test passed-go ahead and define the next method
As the other methods were pretty much the same I knew it would work but you should follow it step by step as the failed tests in a way guide you to write the next bit of code.
You can now see that all tests have passed!
Refactoring
Although these tests and code work, it’s not following the principles of DRY (Don’t Repeat Yourself). There are actually a lot of repetitions in this current code therefore we will be refactoring (restructuring/cleaning up the code) it.
In the spec file, I am repeating the instance variable fizz_buzz = FizzBuzz.new in all of my tests therefore I can actually refactor this into one line like so:
After running RSpec the test still passes!
Now we can start to refactor our code. Instead of defining a method for each method, I can define one and use control flow.
Since I changed the method name, this also needs to reflect in my spec file:
I’m sure I can refactor this further however these are the only ways I know of right now. Hopefully as I learn more about Ruby and rspec, I will come across many other ways in which the code can be written.
EDIT
Ok so after thinking around for a bit, I have changed 2 things:
-
I realised that my tests do not have a test for when a number is not divisible by 3, 5, or 15.
-
Change the wording in my spec file.
-
Refactoring the program code.
How my spec file looks like now:
As you can see, I am now more explicit in my description of my tests which makes it clearer as to what the tests are testing (and what the code should do).
I also now have a negative test case for when a number is not divisible by each specified number. This means I no longer need to have the ‘returns any other number’ test case.
This is better because I’m being more specific in terms of writing the program. Previously, without having the negative test case, when I wrote this bit of code:
The test would pass when it shouldn’t because I want my program to have specified the control flow of only printing “Fizz” when divisible by 3.
Now because I have changed my test back to the method names of divisible_by_three/five/fifteen instead of divisible_by, my code no longer works. This was a tricky situation for me as I did not know how I could refactor the code whilst having 3 different method names in my spec file! However someone pointed out to me that I can call a class method within the class!!!!!!!!!!!!! 😪 😱 😭
Take a look:
Can you see how I am still defining the individual methods but calling the universal method inside them? At the moment I feel like this is the only way to refactor without having to define all the methods! I have made the specific methods private so that they cannot be called outside of the class.
If anybody knows of another way to do this please let me know in the comments! 🤓
- - -
This blog post has also been published on medium.