We have seen this Kata on this post: http://trikalabs.com/fizzbuzz-kata/ so you can practise it first alone if you prefer.
Let’s start by creating a new project. Since we want to be able to reuse the FizzBuzz logic on many projects, we will create a Framework. Open the Xcode and select File > New > iOS > Cocoa Framework.
Let’s name the project FizzBuzz. Make sure you select the Unit Tests checkbox.
Xcode creates the following files for us:
Select the FizzBuzzTests.swift file. Xcode has already created the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import XCTest @testable import FizzBuzz class FizzBuzzTests: XCTestCase { override func setUpWithError() throws { // Put setup code here. This method is called before the invocation of each test method in the class. } override func tearDownWithError() throws { // Put teardown code here. This method is called after the invocation of each test method in the class. } func testExample() throws { // This is an example of a functional test case. // Use XCTAssert and related functions to verify your tests produce the correct results. } func testPerformanceExample() throws { // This is an example of a performance test case. self.measure { // Put the code you want to measure the time of here. } } } |
This code comes as part of XCTestCase creation template and can be modified to our preferences. We will explore later what all the automatic created methods do, but for now, let’s delete them so we can start from scratch. Also, delete the line: @testable import FizzBuzz.
The code should look like this:
1 2 3 4 5 |
import XCTest class FizzBuzzTests: XCTestCase { } |
We are left with the import of the XCTest framework and the FizzBuzzTests class that inherits from the XCTestCase. XCTest is the framework that helps us to create and run unit tests, so we have to import it in all our test classes. XCTestCase is the primary class for defining test methods. It also provides methods we can use for the setup and teardown of our test methods. Eventually, all our TestCases have to be inherited from XCTestCase.
Now we need to think about how to start. TDD theory says we should start by writing a failing test. But what should that test be? Kent Beck, in his book “Test-Driven Development: By Example”, recommends beginning with the most straightforward test. So what is the simplest test in our case? By reading the requirements, it looks like the simplest test is that FizzBuzz returns the input number if the number is not divisible by three or five. So, let’s start by creating this test method.
For a XCTestCase to recognise a method as a test, the method’s name must start with the prefix test. So, let’s give that method the name test_fizzBuzz_whenInputIs1_returns1. We use the prefix test to make this method a test method; then we follow the convention test_methodUnderTest_inputs_outputs. This format helps our tests to be more readable. It also helps us to use the test name as documentation of what the code does. This can enable us to immediately find what is broken when we are looking on a failing tests’ list.
Now we need to create the object that will calculate the FizzBuzz output. Since the object will calculate the output, let’s give it the name FizzBuzzCalculator:
1 2 3 |
func test_fizzBuzz_whenInputIs1_returns1() { let fizzBuzzCalculator = FizzBuzzCalculator() } |
For now, let’s ignore the warnings from the static analyser. In this step, we want to focus on how we want our code’s design to be. We also don’t care if the static analyser shows a lot of errors or if the functionality does not exist. We should write the test as we want our ideal design.
We want the FizzBuzzCalculator to calculate the fizzBuzz result, so we have to invoke a method to get the result:
1 2 3 4 5 |
func test_fizzBuzz_whenInputIs1_returns1() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 1) } |
Now we have to assert if the result is what we expect. We will use one of the many assertion methods with which the XCTest provides us:
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIs1_returns1() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 1) XCTAssertEqual("1", result) } |
We use XCTAssertEqual when we want to compare two values.
Okay, now we have written our first test! Let’s run it by tapping the diamond on the left-hand side of the test method:
The code fails to compile. Let’s fix the error to make it compile. Our first static analyser error is “Use of unresolved identifier ‘FizzBuzzCalculator’”, which means the FizzBuzzCalculator class does not exist, or at least it is not visible to our test class. Let’s create it. Please make sure you add the file in the main target and not the test target.
Add the following code:
1 2 3 4 5 6 7 8 |
import Foundation public struct FizzBuzzCalculator { public init() { } } |
All we did is add the public keyword in front of the struct declaration and make the init method public.
Now to make it visible in our test target, let’s import the main module after the XCTest import.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
import XCTest import FizzBuzz class FizzBuzzTests: XCTestCase { func test_fizzBuzz_whenInputIs1_returns1() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 1) XCTAssertEqual("1", result) } } |
Tap on the diamond button to run the test or tap CMD + U to run all unit tests. The test is still fails, but this time, we get a different error: “Value of type ‘FizzBuzzCalculator’ has no member ‘fizzBuzz'”. Now the error is that the FizzBuzz method does not exist or is not public. Let’s add this method:
1 2 3 4 5 6 7 8 9 10 11 12 |
import Foundation public struct FizzBuzzCalculator { public init() { } public func fizzBuzz(number: Int) -> String { return "" } } |
The method returns an empty string, which is the simplest implementation we can have here. We could have opted to return the input number and make the test pass, but as best practise, we should use the simplest string, which is the empty string—at least while we learn the basics. We also want to see the test fail before we add our implementation. Tap again CMD + U. This time the code compiles, and the test fails as expected.
Now let’s make it pass. Remember we are trying to make it pass in the simplest way, which is returning 1.
1 2 3 |
public func fizzBuzz(number: Int) -> String { return "1" } |
We have already finished the RED and the GREEN steps, and we move to the REFACTOR step. Can we refactor something? Has our code any duplication, hidden abstraction, or ugly solution? Currently, the code is very simple, so no need to refactor. Now let’s deal with the next test. Choosing the number 2 seems like a good choice for the next simplest test. Let’s add it.
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIs2_returns2() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 2) XCTAssertEqual("2", result) } |
We use a very similar format to the first test, the only difference being that we invoke the FizzBuzz method with the number 2.
Likely, this time it compiles, so run the test to see it fails. The simplest way to make our test pass is by introducing an “if” clause—we will return “2” if the input number is 2, or “1” otherwise.
1 2 3 4 5 6 |
public func fizzBuzz(number: Int) -> String { if number == 2 { return "2" } return "1" } |
Let’s run our tests. Great! All tests are passing, so now is time for the refactor step. Can we refactor? Again, the code is very simple, so no need to refactor at the moment.
So, what should be our next simplest test? Shall we choose the number 3? The number 3 is special number in our exercise, so let’s leave it for now (we will return to it soon). Let’s pick 4 for our next test:
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIs4_returns4() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 4) XCTAssertEqual("4", result) } |
Run the test to see it fails, and let’s make it pass in the simplest way, which is to add an extra “else if” clause:
1 2 3 4 5 6 7 8 |
public func fizzBuzz(number: Int) -> String { if number == 4 { return "4" } else if number == 2 { return "2" } return "1" } |
You may wonder that if we have to write a test for all the Integers, this procedure could go on forever. Remember that in this chapter, I want to show you the TDD cycle in detail and to get you used to some of the TDD techniques and principles. When you master these techniques, you will be able to choose which one to follow and when. The first technique is the Triangulation—this technique is discussed in detail in the Kent Becks’ book “Test-Driven Development: By Example”. This technique tells us that when we have at least two tests to cover the same case, we can generalise. So, in our case, we can write the code as:
1 2 3 |
public func fizzBuzz(number: Int) -> String { return "\(number)" } |
After this refactor, we have to make sure existing functionality works as before. So run all tests to verify all the current functionality is still okay.
But why haven’t we returned the input number as string form the beginning when it looks so obvious? Actually, we could have added it after seeing the first test fail. Kent Beck names this technique “Obvious Implementation”. For such simple cases, we can use the Obvious Implementation, but for more complex cases, Triangulation is recommended.
Finally, the first requirement has been implemented. Let’s now select the next simplest one, which appears to be either “FizzBuzz returns Fizz if the number is divisible by 3” or “FizzBuzz returns Buzz if the number is divisible by 5”. Let’s choose the first one.
As usual, we start with a failing test.
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIs3_returnsFizz() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 3) XCTAssertEqual("Fizz", result) } |
Now we will add the simplest implementation to make it pass. As we saw before, it is straightforward to make it pass by adding an if clause:
1 2 3 4 5 6 |
public func fizzBuzz(number: Int) -> String { if number == 3 { return "Fizz" } return "\(number)" } |
Let’s now add the test for the number 6 which is a multiple of 3.
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIsMultipleOf3_returnsFizz() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 2 * 3) XCTAssertEqual("Fizz", result) } |
This time, we can use the obvious implementation to make it pass:
1 2 3 4 5 6 |
public func fizzBuzz(number: Int) -> String { if number % 3 == 0 { return "Fizz" } return "\(number)" } |
Another requirement has been implemented. Let’s now do the next simplest requirement: FizzBuzz returns Buzz if the number is divisible by 5.
Let’s add the code for our new test:
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIs5_returnsBuzz() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 5) XCTAssertEqual("Buzz", result) } |
We can simply add an extra “if” clause to make it pass:
1 2 3 4 5 6 7 8 |
public func fizzBuzz(number: Int) -> String { if number == 5 { return "Buzz" } else if number % 3 == 0 { return "Fizz" } return "\(number)" } |
Now let’s tackle a multiple of 5. As always start with a failing test:
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIsMultipleOf5_returnsBuzz() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 2 * 5) XCTAssertEqual("Buzz", result) } |
Replacing “ == “ with “ % “ 5 is enough to make it pass:
1 2 3 4 5 6 7 8 |
public func fizzBuzz(number: Int) -> String { if number % 5 == 0 { return "Buzz" } else if number % 3 == 0 { return "Fizz" } return "\(number)" } |
As always, after the green step, we have to think about if we can refactor. In this case, we can. We notice there is a code duplication on checking if the number is a multiple of another. Let’s extract this functionality in a utility method.
1 2 3 4 5 6 7 8 9 10 11 12 |
public func fizzBuzz(number: Int) -> String { if isDivisibleBy(numerator: number, denominator: 5) { return "Buzz" } else if isDivisibleBy(numerator: number, denominator: 3) { return "Fizz" } return "\(number)" } private func isDivisibleBy(numerator: Int, denominator: Int) -> Bool { return numerator % denominator == 0 } |
After a refactor, we should always check our tests. Run the tests and verify they all pass. Finally, let’s implement the last requirement:“FizzBuzz returns FizzBuzz if the number is divisible by 5 and by 3”. Our failing test uses the number 3 * 5—a multiple of 3 and 5:
1 2 3 4 5 6 7 |
func test_fizzBuzz_whenInputIsMultipleOf3And5_returnsFizzBuzz() { let fizzBuzzCalculator = FizzBuzzCalculator() let result = fizzBuzzCalculator.fizzBuzz(number: 3 * 5) XCTAssertEqual("FizzBuzz", result) } |
The implementation should be similar to the previous cases:
1 2 3 4 5 6 7 8 9 10 |
public func fizzBuzz(number: Int) -> String { if isDivisibleBy(numerator: number, denominator: 3) && isDivisibleBy(numerator: number, denominator: 5) { return "FizzBuzz" } else if isDivisibleBy(numerator: number, denominator: 5) { return "Buzz" } else if isDivisibleBy(numerator: number, denominator: 3) { return "Fizz" } return "\(number)" } |
CMD + U to run all the tests. They should all pass, and all requirements have now been implemented!