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.

Leave a rating/review
Download materials
Save for later

Test Driven Development (TDD) is a popular way to write software. The methodology dictates that you write tests before writing supporting code. While this may seem backward, it has some nice benefits.

One such benefit is that the tests provide documentation about how a developer expects the app to behave. This documentation stays current because test cases are updated alongside the code, which is great for developers who aren’t great at creating or maintaining documentation.

Another benefit is that apps developed using TDD result in better code coverage. Tests and code go hand-in-hand, making extraneous, untested code unlikely.

TDD lends itself well to pair-programming, where one developer writes tests and the other writes code to pass the tests. This can lead to faster development cycles as well as more robust code.

Lastly, developers who use TDD have an easier time when it comes to making major refactors in the future. This is a by-product of the fantastic test coverage for which TDD is known.

In this Test Driven Development tutorial, you’ll use TDD to build a Roman numeral converter for the Numero app. Along the way, you’ll become familiar with the TDD flow and gain insight into what makes TDD so powerful.

Getting Started

To kick things off, start by downloading the materials for this tutorial (you can find a link at the top and bottom of this tutorial). Build and run the app. You’ll see something like this:

The app displays a number and a Roman numeral. The player must choose whether or not the Roman numeral is the correct representation of the number. After making a choice, the game displays the next set of numbers. The game ends after ten attempts, at which point the player can restart the game.

Try playing the game. You’ll quickly realize that “ABCD” represents a correct conversion. That’s because the real conversion has yet to be implemented. You’ll take care of that during this tutorial.

Take a look at the project in Xcode. These are the main files:

  • ViewController.swift: Controls the gameplay and displays the game view.
  • GameDoneViewController.swift: Displays the final score and a button to restart the game.
  • Game.swift: Represents the game engine.
  • Converter.swift: Model representing a Roman numeral converter. It’s currently empty.

Mostly, you’ll be working with Converter and a converter test class that you’ll create next.

Note: This may be a good time to brush up on your Roman numeral skills.

Creating Your First Test and Functionality

The typical TDD flow can be described in the red-green-refactor cycle:

It consists of:

  1. Red: Writing a failing test.
  2. Green: Writing just enough code to make the test pass.
  3. Refactor: Cleaning up and optimizing your code.
  4. Repeating the previous steps until you’re satisfied that you’ve covered all the use cases.

Creating a Unit Test Case Class

Create a new Unit Test Case Class template file under NumeroTests, and name it ConverterTests:

Open ConverterTests.swift and delete testExample() and testPerformanceExample().

Add the following just after the import statement at the top:

@testable import Numero

This gives the unit tests access to the classes and methods in Numero.

At the top of the ConverterTests class, add the following property:

let converter = Converter()

This initializes a new Converter object that you’ll use throughout your tests.

Writing Your First Test

At the end of the class, add the following new test method:

func testConversionForOne() {
  let result = converter.convert(1)

The test calls convert(_:) and stores the result. As this method has yet to be defined, you’ll see the following compiler error in Xcode:

In Converter.swift, add the following method to the class:

func convert(_ number: Int) -> String {
  return ""

This takes care of the compiler error.

Note: If the compiler error doesn’t go away, try commenting out the line that imports Numero, and then uncomment the same line. If that doesn’t work, select ProductBuild ForTesting from the menu.

In ConverterTests.swift, add the following to the end of testConversionForOne():

XCTAssertEqual(result, "I", "Conversion for 1 is incorrect")

This uses XCTAssertEqual to check the expected conversion result.

Press Command-U to run all the tests (of which there’s currently only one). The simulator should start but you’re more interested in the Xcode test results:

You’ve come to the first step of a typical TDD cycle: Writing a failing test. Next, you’ll work on making this test pass.

Fixing Your First Failure

Back in Converter.swift, replace convert(_:) with the following:

func convert(_ number: Int) -> String {
  return "I"

The key is writing just enough code to make the test pass. In this case, you’re returning the expected result for the only test you have thus far.

To run it — and because there’s only one test — you can press the play button next to the test method name in ConverterTests.swift:

The test now passes:

The reason why you start with a failing test and then fix your code to pass it is to avoid a false-positive. If you never see your tests fail, you can’t be sure you’re testing the right scenario.

Pat yourself on the back for getting through your first TDD run!

But don’t celebrate too long. There’s more work to do, because what good is a Roman Numeral converter that only handles one number?

Extending the Functionality

Working on Test #2

How about trying out the conversion for 2? That sounds like an excellent next step.

In ConverterTests.swift, add the following new test to the end of the class:

func testConversionForTwo() {
  let result = converter.convert(2)
  XCTAssertEqual(result, "II", "Conversion for 2 is incorrect")

This tests the expected result for 2 which is II.

Run your new test. You’ll see a failure because you haven’t added code to handle this scenario:

In Converter.swift, replace convert(_:) with the following:

func convert(_ number: Int) -> String {
  return String(repeating: "I", count: number)

The code returns I, repeated a number of times based on the input. This covers both cases you’ve tested thus far.

Run all of the tests to make sure your changes haven’t introduced a regression. They should all pass: