Test Driven Development Tutorial for iOS: Getting Started
In this Test Driven Development Tutorial, you will learn the basics of TDD and how to be effective at it as an iOS developer. By Christine Abernathy.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Test Driven Development Tutorial for iOS: Getting Started
25 mins
- Getting Started
- Creating Your First Test and Functionality
- Creating a Unit Test Case Class
- Writing Your First Test
- Fixing Your First Failure
- Extending the Functionality
- Working on Test #2
- Working on Test #3
- Working on Test #4
- Working on Test #5
- Uncovering a Pattern
- Handling the Special Cases
- Refactoring
- Exposing the Duplicate Code
- Optimizing Your Code
- Handling Other Edge Cases
- Use Your Converter
- Other Test Methodologies
- Where to Go From Here?
Refactoring
Recognizing duplicate code and cleaning it up, also known as refactoring, is an essential step in the TDD cycle.
At the end of the previous section, a pattern emerged in the conversion logic. You’re going to identify this pattern fully.
Exposing the Duplicate Code
Still in Converter.swift, take a look at the conversion method:
func convert(_ number: Int) -> String {
var result = ""
var localNumber = number
while localNumber >= 10 {
result += "X"
localNumber = localNumber - 10
}
if localNumber >= 9 {
result += "IX"
localNumber = localNumber - 9
}
if localNumber >= 5 {
result += "V"
localNumber = localNumber - 5
}
if localNumber >= 4 {
result += "IV"
localNumber = localNumber - 4
}
result += String(repeating: "I", count: localNumber)
return result
}
To highlight the code duplication, modify convert(_:)
and change every occurrence of if
with while
.
To make sure you haven’t introduced a regression, run all of your tests. They should still pass:
That’s the beauty of cleaning up your code and refactoring with TDD methodology. You can have the peace of mind that you aren’t breaking existing functionality.
There’s one more change that will fully expose the duplication. Modify convert(_:)
and replace:
result += String(repeating: "I", count: localNumber)
With the following:
while localNumber >= 1 {
result += "I"
localNumber = localNumber - 1
}
These two pieces of code are equivalent and return a repeating I string.
Run all of your tests. They should all pass:
Optimizing Your Code
Continue refactoring the code in convert(_:)
by replacing the while
statement that handles 10 with the following:
let numberSymbols: [(number: Int, symbol: String)] // 1
= [(10, "X")] // 2
for item in numberSymbols { // 3
while localNumber >= item.number { // 4
result += item.symbol
localNumber = localNumber - item.number
}
}
Let’s go through the code step-by-step:
- Create an array of tuples representing a number and the corresponding Roman numeral symbol.
- Initialize the array with values for 10.
- Loop through the array.
- Run each item in the array through the pattern you uncovered for handling the conversion for a number.
Run all of your tests. They continue to pass:
You should now be able to take your refactoring to its logical conclusion. Replace convert(_:)
with the following:
func convert(_ number: Int) -> String {
var localNumber = number
var result = ""
let numberSymbols: [(number: Int, symbol: String)] =
[(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I")]
for item in numberSymbols {
while localNumber >= item.number {
result += item.symbol
localNumber = localNumber - item.number
}
}
return result
}
This initializes numberSymbols
with additional numbers and symbols. It then replaces the previous code for each number with the generalized code you added to process 10.
Run all of your tests. They all pass:
Handling Other Edge Cases
Your converter has come a long way, but there are more cases you can cover. You’re now equipped with all the tools you need to make this happen.
Start with the conversion for zero. Keep in mind, however, zero isn’t represented in Roman numerals. That means, you can choose to throw an exception when this is passed or just return an empty string.
In ConverterTests.swift, add the following new test to the end of the class:
func testConverstionForZero() {
let result = converter.convert(0)
XCTAssertEqual(result, "", "Conversion for 0 is incorrect")
}
This tests the expected result for zero and expects an empty string.
Run your new test. This works by virtue of how you’ve written your code:
Try testing for the last number that’s supported in Numero: 3999.
In ConverterTests.swift, add the following new test to the end of the class:
func testConverstionFor3999() {
let result = converter.convert(3999)
XCTAssertEqual(result, "MMMCMXCIX", "Conversion for 3999 is incorrect")
}
This tests the expected result for 3999.
Run your new test. You’ll see a failure because you haven’t added code to handle this edge case:
In Converter.swift, modify convert(_:)
and change the numberSymbols
initialization as follows:
let numberSymbols: [(number: Int, symbol: String)] =
[(1000, "M"),
(900, "CM"),
(500, "D"),
(400, "CD"),
(100, "C"),
(90, "XC"),
(50, "L"),
(40, "XL"),
(10, "X"),
(9, "IX"),
(5, "V"),
(4, "IV"),
(1, "I")]
This code adds mappings for the relevant numbers from 40 through 1,000. This also covers the test for 3,999.
Run all of your tests. They all pass:
If you fully bought into TDD, you likely protested about adding numberSymbols
mappings for say 40 and 400 as they’re not covered by any tests. That’s correct! With TDD, you don’t want to add any code unless you’ve first written tests. That’s how you keep your code coverage up. I’ll leave you with the exercise of righting these wrongs in your copious free time.
Note: Special mention goes to Jim Weirich – Roman Numerals Kata for the algorithm behind the app.
Note: Special mention goes to Jim Weirich – Roman Numerals Kata for the algorithm behind the app.
Use Your Converter
Congratulations! You now have a fully functioning Roman numeral converter. To try it out in the game, you’ll need to make a few more changes.
In Game.swift, modify generateAnswers(_:number:)
and replace the correctAnswer
assignment with the following:
let correctAnswer = converter.convert(number)
This switches to using your converter instead of the hard-coded value.
Build and run your app:
Play a few rounds to make sure all the cases are covered.
Other Test Methodologies
As you dive more into TDD, you may hear about other test methodologies, for example:
- Acceptance Test-Driven Development (ATDD): Similar to TDD, but the customer and developers write the acceptance tests in collaboration. A product manager is an example of a customer, and acceptance tests are sometimes called functional tests. The testing happens at the interface level, generally from a user point of view.
- Behavior-Driven Development (BDD): Describes how you should write tests including TDD tests. BDD advocates for testing desired behavior rather than implementation details. This shows up in how you structure a unit test. In iOS, you can use the given-when-then format. In this format, you first set up any values you need, then execute the code being tested, before finally checking the result.