Unit Testing on macOS: Part 1/2

In this Unit testing tutorial for macOS you’ll learn how unit tests can help you to write stable code and give you the confidence that changes don’t break your code. By Sarah Reichelt.

Leave a rating/review
Save for later
Share

Unit testing is one of those things that we all know deep down we should be doing, but it seems too difficult, too boring, or too much like hard work.

It’s so much fun creating code that does exciting things; why would anyone want to spend half the time writing code that just checks things?

The reason is confidence! In this Unit testing on macOS tutorial, you’ll learn how to test your code and you will gain confidence that your code is doing what you want it to do, confidence that you can make major changes to your code and confidence that you won’t break anything.

Getting Started

This project uses Swift 3 and requires, at a minimum, Xcode 8 beta 6. Download the starter project and open it in Xcode.

If you have done any other tutorials here at raywenderlich.com, you are probably expecting to build and run at this stage, but not this time — you are going to test. Go to the Product menu and choose Test. Note the shortcut — Command-U — you’ll be using it a lot.

When you run the tests, Xcode will build the app and you will see the app window appear a couple of times before you get a message saying “Test Succeeded”. In the Navigator pane on the left, select Test navigator.

TestNavigator2

This shows you the three tests added by default; each one has a green tick beside it, showing that the test passed. To see the file containing those tests, click on the second line in the Test Navigator where it says High RollerTests preceded by an uppercase T icon.

DefaultTests3

There are a few important things to note here:

  • The imports: XCTest is the testing framework provided by Xcode. @testable import High_Roller is the import that gives the testing code access to all the code in the High_Roller module. Every test file will need these two imports.
  • setup() and tearDown(): these are called before and after every single test method.
  • testExample() and testPerformanceExample(): actual tests. The first one tests functionality, and the second one tests performance. Every test function name must begin with test so that Xcode can recognize it as a test to perform.

What Is Unit Testing?

Before you get into writing your own tests, it’s time for a brief discussion about unit testing, what it actually is and why you should use it.

A unit test is a function that tests a single piece — or unit — of your code. It doesn’t get included in the code of your application, but is used during development to check that your code does what you expected.

A common first reaction to unit tests is: “Are you telling me I should write twice as much code? One function for the app itself and another to test that function?” Actually, it can be worse than that — some projects end up with more testing code than production code.

At first, this seems like a terrible waste of time and effort — but wait until a test catches something that you didn’t spot, or alerts you to a side-effect of re-factoring. That’s when you realize what an amazing tool this is. After a while, any project without unit tests feels very fragile, and you’ll hesitate to make any changes because you cannot be sure what will happen.

Test Driven Development

Test Driven Development (TDD) is a branch of unit testing where you start with the tests and only write code as required by the tests. Again, this seems like a very strange way to proceed at first and can produce some very peculiar code as you’ll see in a minute. The upshot is that this process really makes you think about the purpose of the code before coding begins.

Test Driven Development has three repeating steps:

  1. Red: Write a failing test.
  2. Green: Write the minimum code needed to make the test pass.
  3. Refactor: Optional; if any app or test code can be re-factored to make it better, do it now.

This sequence is important and the key to effective TDD. Fixing a failing test gives you a clear indication you know exactly what your code is doing. If your test passes the first time, without any new code being written, then you have not correctly pin-pointed the next stage of development.

To start, you’ll write a series of tests and the accompanying code using TDD.

dog

The Test Project

This project is a dice rolling utility for board gamers. Ever sit down to play a game with the family and discover that the dog ate the dice? Now your app can come to the rescue. And if anyone says “I don’t trust a computer not to cheat!” you can proudly say that the app has been unit tested to prove that it works correctly. That’s bound to impress the family — and you will have saved games night. :]

The model for this app will have two main object types: Dice, which will have a value property and a method for generating a random value, and Roll, which will be a collection of Dice objects with methods for rolling them all, totaling the values and so on.

This first test class is for the Dice object type.

The Dice Test Class

In Xcode go to the File Navigator and select the High RollerTests group. Select File\New\File… and choose macOS\Unit Test Case Class. Click Next and name the class DiceTests. Make sure the language is set to Swift. Click Next and Create.

Select all the code inside the class and delete it. Add the following statement to DiceTests.swift just under the import XCTest line:

@testable import High_Roller

Now you can delete HighRollerTests.swift as you don’t need the default tests any longer.

The first thing to test is whether a Dice object can be created.

Your First Test

Inside the DiceTests class, add the following test function:

  func testForDice() {
    let _ = Dice()
  }

This gives a compile error before you can even run the test: "Use of unresolved identifier 'Dice'". In TDD, a test that fails to compile is considered a failing test, so you have just completed step 1 of the TDD sequence.

To make this test pass with the minimum of code, go to the File Navigator and select the Model group in the main High Roller group. Use File\New\File… to create a new Swift file and name it Dice.swift.

Add the following code to the file:

struct Dice {

}

Go back to DiceTests.swift; the error will still be visible until the next build. However, you can now run the test in several different ways.

If you click the diamond in the margin beside the test function, only that single test will run. Try that now, and the diamond will turn into a green checkmark symbol, showing that the test has passed.

You can click a green symbol (or a red symbol that shows a failed test) at any time to run a test. There will now be another green symbol beside the class name. Clicking this will run all the tests in the class. At the moment, this is the same as running the single test, but that will soon change.

The final way to test your code is to run all the tests.

RunningTests

Press Command-U to run all the tests and then go to the Test Navigator where you will see your single test in the High RollerTests section; you may need to expand the sections to see it. The green checkmark symbols appear beside every test. If you move the mouse pointer up and down the list of tests, you will see small play buttons appear which you can use to run any test or set of tests.

In the Test Navigator, you can see that the High RollerUITests ran as well. The problem with UI Tests are that they’re slow. You want your tests to be fast as possible so that there is no drawback to testing frequently. To solve this problem, edit the scheme so that the UI Tests don’t run automatically.

Go to the scheme popup in the toolbar and select Edit scheme…. Click Test in the pane on the left and un-check High RollerUITests. Close the scheme window and run your tests again with Command-U. The UI Tests are faded out in the Test Navigator, but they can still be run manually.

TurnOffUITests

Contributors

Over 300 content creators. Join our team.