Motivation
Yesterday night I decided to learn Swift, and I decided to learn it the hard way. Test first. I’m not going to go into great detail about why people code test first or what the benefits are, but the main idea is that I’ll be writing test cases for all my code before I write the actual code and since I don’t do this normally, it’s gonna be tough.
I was mostly inspired by a Swift TDD Blog Post I came across when searching for information about Swift. The author of the blog post didn’t get very far, however, which was very frustrating for me.
For my first TDD project in Swift I’ll be using the same String Calculator Kata as defined by Roy Osherove’s website. You can view the original instructions on his site too. To reiterate in case for whatever reason his site goes down, I copied the instructions here at onebigfunction. I think I might also keep a list of kata here as well that I find across the net (with links to their sources, of course).
I’m writing this post in hind site. The kata, which should have taken 15 minutes or less, has actually taken me 2 days to complete—mostly due to some of Swift’s oddities, some of which I’m still trying to understand. All together maybe it actually took me 3–4 hours.
You can view my complete solution on GitHub.
Let’s Code!
Let’s get started shell we?
Creating the Project
I started off by creating an empty project. Actually… That’s a lie. What I really did was start off by creating a new iOS Single View Application and gutted it. Here’s how:
-
Create your new iOS Single View Application
-
Enter your product name, I used StringCalculatorKata. Make sure to select Swift as your language (since that’s obviously what we’re doing here)
- Next, in your Project Navigator right click the following files and Delete them. You can also Move to Trash if you don’t want the useless files messying up your project.
- LaunchScreen.xib
- ViewController.swift
- Main.storyboard
- Images.xcassets
- Under Supporting Files open your app’s Info.plist and remove the following two keys by selecting them and pressing the Backspace/Delete key.
- Launch screen interface file base name
- Main storyboard file base name
That leaves us with only our AppDelegate.swift and StringCalculatorKataTests.swift class, which is in our test target. If you did everything right you should be able to run the app and get a black screen. Yay! The app didn’t crash!
Bring on the Tests!
Now we can start throwing some tests in there.
Our First Test
Our first test is going to be the testEmptyStringReturnsZero
method, we will write this before creating our StringCalculator class… Because that’s the point! We’re going to focus on one thing and only one thing at any time, while making sure our previous test cases all pass. I think my first test looked something like this:
func testEmptyStringReturnsZero() {
var stringCalculator = StringCalculator()
XCTAssertEqual(0, stringCalculator.add(""))
}
This time if you did everything right, your code doesn’t compile! Pat yourself on the back and move on, cause it’s time to make this code compile and pass.
Add the StringCalculator class
Generally when I’m working with Xcode (usually in Objective-C) I tend to create my folder structure from the command line—cause let’s face it, mkdir -p
is so much faster than finder or anything else.
This is the directory structure I’m going for:
.
├── StringCalculatorKata
│ ├── AppDelegate.swift
│ ├── Info.plist
│ └── classes
│ └── StringCalculator
│ └── StringCalculator.swift
so from our root StringCalculatorKata directory run the following:
mkdir -p classes/StringCalculator
Then, in Xcode’s Project Navigator, right click your base project folder (StringCalculatorKata/
in my case) and click Add Files to StringCalculatorKata… then select only the classes/
directory, the same hierarchy of folders we created should show up now in your project navigator.
In the empty StringCalculator/
directory of the Project Navigator right click and select Cocoa Touch Class. Make it a Subclass of: NSObject
, and name it StringCalculator
, since that’s what we used in our test.
Make sure to add the new class to our test target by clicking on the StringCalculator.swift file in the Project Navigator then placing a check in the StringCalculatorKataTests field under Target Membership in the File inspector (⌘ 0)
The empty class looks like this:
import UIKit
class StringCalculator: NSObject {
}
To make it pass we need to create our add
method and make it return 0
. Simple enough, right?
func add(var numberString: String) -> Int {
return 0
}
Run the test, and everything should pass this time.
Test that adding one number returns that number
Let’s add a new test now to check that if we pass it a single number it will work:
func testOneNumberZeroReturnsSum() {
var stringCalculator = StringCalculator()
XCTAssertEqual(0, stringCalculator.add("0"))
}
Run the tests again. All tests pass, however we know this is a fluke. I added another test for a single number, with a different number:
func testOneNumberActuallyReturnsSum() {
var stringCalculator = StringCalculator()
XCTAssertEqual(1, stringCalculator.add("1"))
}
This test should fail. Let’s make it pass. Go back into our StringCalculator class and change the add method:
func add(var numberString: String) -> Int {
if (countElements(numberString) == 1) {
return numberString.toInt()!
}
return 0;
}
Here we can use the countElements()
function to count the number of characters in a string. Strings work quite differently in Swift, I will write more on this later. If you want to see more about strings check StackOverflow: Get the length of a String and Get number value from string in swift.
Code duplication warning: Even though it’s only in our tests I’d like to point out that we are starting to see some duplication. So far we have 3 tests, each one declaring and instantiating a new var stringCalculator = StringCalculator()
. Now’s a good time to refactor this and pull it out into a central area. We can do this by declaring a var
outside the function scope. My new test class looks like this:
class StringCalculatorTests: XCTestCase {
var _stringCalculator: StringCalculator = StringCalculator();
override func setUp() {
super.setUp()
}
override func tearDown() {
super.tearDown()
}
func testEmptyStringReturnsZero() {
XCTAssertEqual(0, _stringCalculator.add(""))
}
func testOneNumberZeroReturnsSum() {
XCTAssertEqual(0, _stringCalculator.add("0"))
}
func testOneNumberActuallyReturnsSum() {
XCTAssertEqual(1, _stringCalculator.add("1"))
}
}
Run the tests again just in case.
Some of you might be concerned about what happens after every test case though. Don’t worry, we are actually getting a new object each time. Check out Unit Testing in Swift over at Bendyworks for more detailed information, see the section about Using a Common Object.
Optional values: My first time through the code, I didn’t quite catch this. But now that I’m looking at it again returning string.toInt()!
directly could be dangerous since we might be unwrapping a nil, so we should probably check for it:
class StringCalculator: NSObject {
func add(var numberString: String) -> Int {
if (countElements(numberString) == 1) {
let numericValue: Int? = numberString.toInt()
if numericValue != nil {
return numericValue!
}
}
return 0;
}
}
I got the idea from this from a different StackOverflow article: Swift - Converting String to Int. Run the tests again. All tests pass.
Support adding two numbers
Let’s add support for adding two numbers in our string. According to the instructions calling StringCalculator.add("1,2")
should return a value of 3
.
Add the test:
func testTwoNumbersReturnsSum() {
XCTAssertEqual(3, _stringCalculator.add("1,2"))
}
Run the tests. Our new test should fail, let’s make it pass.
func add(var numberString: String) -> Int {
if (countElements(numberString) == 1) {
let numericValue: Int? = numberString.toInt()
if numericValue != nil {
return numericValue!
}
}
if (countElements(numberString) == 3) {
let numberArray = numberString.componentsSeparatedByString(",")
return numberArray[0].toInt()! + numberArray[1].toInt()!
}
return 0;
}
If you’re not familiar with TDD you are probably ready to reach into your screen and strangle me right about now. Yes, there are problems with this code. Yes, they are a clear and present danger. Relax, we will handle them in subsequent iterations. For now our tests are passing and I wrote too much already so I’ll stop for today and post more about this later. In case you missed the link at the top, you can check out my entire solution on GitHub.
By the way, I got the tip about String.componentsSeparatedByString()
from the String reference guide for Swift at LearnSwiftOnline.
I will update this article later so that I can add some of the missing screenshots from the top part also.