Getting Started with Unit Testing in Swift 4

June 13th, 2018

Filed under: Cocoa, iOS Development, Mac Development, Xcode | Be the first to comment!

One of the most read articles on this blog is An Introduction to Swift Unit Testing. The article is almost four years old and uses Swift 1. Because of that I have decided to build upon that article here, updating the code to Swift 4 and adding an example project to unit test.

The Project

I am going to add unit tests to TemperatureConverter, a project that converts temperatures from Celsius to Fahrenheit. I wrote TemperatureConverter as an introduction to Cocoa in my Creating a Simple Mac Application Using Cocoa, Swift, and Storyboards article. You can either go through the article and create the project, or you can download the project from GitHub. The unit-testing branch contains the unit tests.

If you go through the article and create the project, make sure you select the Include Unit Tests checkbox when creating the project. You need a unit test target to do unit testing in Xcode.

Whether you follow the article and create the project or download it from GitHub, keep in mind that I removed the UI test target in my version of the project. I don’t want the additional target to get in the way. If you download the project from GitHub and keep the UI test target, you may get build errors when you unit test. The errors occur because I set the deployment target of the project to macOS 10.10, but Xcode UI tests require 10.11. Either change the deployment target to 10.11 or remove the UI test target from the project.

The Unit Tests

In the course of this article, I’m going to add the following unit tests to the TemperatureConverter project:

  • A test that the freezing point of water is 32 degrees Fahrenheit.
  • A test that the boiling point of water is 212 degrees Fahrenheit.
  • A test for negative Celsius temperatures.

The Unit Test File

When you create a project in Xcode that has a unit test target, Xcode creates a unit test file for you. Select the file TemperatureConverterTests.swift to open the unit test file. You should see the following test functions:

  • setUp
  • tearDown
  • testExample
  • testPerformanceExample

You can remove the testExample and testPerformanceExample functions. You’re not going to use them.

The setUp function gets called before every test. If you need to initialize some properties before running your tests, put that code in setUp.

The tearDown function gets called after every test. If there’s any cleanup work to do after running your tests, put that code in tearDown. I’m not going to use setUp or tearDown in this article.

Make sure you see the following line of code before the declaration of the unit testing class:

@testable import TemperatureConverter

If you don’t see it, add that line of code. You need to import the module for the app so you can access the app’s classes in your unit tests.

The First Unit Test

Now it’s time to write the first unit test. Swift unit test functions have the following properties:

  • They start with the word test.
  • They take no arguments.
  • They return no value.

The first unit test is going to test the freezing point of water. You have to perform the following steps to create the unit test:

  1. Create a view controller because the view controller contains the function to convert the temperature.
  2. Supply a Celsius temperature, which will be 0 for this test.
  3. Convert the Celsius temperature to Fahrenheit.
  4. Perform an assertion that the converted temperature equals the freezing point of water.

Xcode unit test assertions start with XCTAssert. If you type XCTAssert in Xcode, the code completion should show you a list of all the possible assertions. The assertion to call for the first test is XCTAssertEqual. XCTAssertEqual asserts that two values are equal. Swift has multiple versions of XCTAssertEqual. The version I use takes the following arguments:

  • Two floating-point values.
  • An accuracy threshold to determine if the two values are equal. Floating-point math can result in values with slight differences, such as giving you the value 31.99999999 instead of 32.0. With a suitable accuracy threshold, 31.99999999 and 32.0 will test as equal.
  • An error message that appears if the test fails.

If this test fails, you want to know what the converted temperature is. To print the value of a variable in an error message, use the following notation:

\(variableName)

The following is the initial version of the test to make sure the test fails:

func testWaterFreezingPoint() {
    let vc = ViewController()
    let celsiusTemperature = Float(0)
    let fahrenheitFreezingPoint = Float(40)

    let fahrenheitTemperature = vc.convert(celsius: celsiusTemperature)
    XCTAssertEqual(fahrenheitTemperature, fahrenheitFreezingPoint, accuracy: 0.01, 
        "The freezing point of water should be 40 degrees Fahrenheit. 
        Fahrenheit Temperature: \(fahrenheitTemperature)")
}

To run the test choose Product > Test in Xcode. The test should fail with an error message telling you that the Fahrenheit temperature is 32.0 degrees. Click the Red icon on the right side of the editor to see the error message. To make the test pass, change the value of fahrenheitFreezingPoint from 40 to 32. You should also change the error message in the assertion to say that the freezing point of water should be 32 degrees.

After making the corrections, choose Product > Test. The test should pass. By supplying the value 0.01 for the accuracy argument, the test will pass if the converted temperature is greater than 31.99 and less than 32.01.

The Second Unit Test

The second test tests the boiling point of water. It’s going to be similar to the first test.

  1. Create a view controller because the view controller contains the function to convert the temperature.
  2. Supply a Celsius temperature, which will be 100 for this test.
  3. Convert the Celsius temperature to Fahrenheit.
  4. Perform an assertion that the converted temperature equals the boiling point of water.

The main difference is using the boiling point of water instead of the freezing point. The following code tests the boiling point of water and should pass:

func testWaterBoilingPoint() {
    let vc = ViewController()
    let celsiusTemperature = Float(100)
    let fahrenheitBoilingPoint = Float(212)

    let fahrenheitTemperature = vc.convert(celsius: celsiusTemperature)
    XCTAssertEqual(fahrenheitTemperature, fahrenheitBoilingPoint, accuracy: 0.01, 
        "The boiling point of water should be 212 degrees Fahrenheit. 
        Fahrenheit Temperature: \(fahrenheitTemperature)")
}

The Final Unit Test

I have one last test to write for this article. The first two tests used non-negative Celsius temperatures. The last test uses a negative Celsius temperature to see if the temperature conversion works.

func testNegativeCelsiusTemperature() {
    let vc = ViewController()
    let celsiusTemperature = Float(-10)
    let fahrenheitExpected = Float(14)

    let fahrenheitTemperature = vc.convert(celsius: celsiusTemperature)
    XCTAssertEqual(fahrenheitTemperature, fahrenheitExpected, accuracy: 0.01, 
        "-10 degrees Celsius should equal 14 degrees Fahrenheit. 
        Fahrenheit Temperature: \(fahrenheitTemperature)")
}

I supply a temperature of –10 degrees Celsius, which should convert to 14 degrees Fahrenheit.

Where to Go from Here?

Three tests do not make an exhaustive test suite. You can practice creating unit tests by adding to the three tests I wrote in this article. I can think of at least two more tests to write: one to test a Celsius temperature that yields a non-integer Fahrenheit temperature and one to test a non-integer Celsius temperature.

To learn more about unit testing with XCTest, read the XCTest documentation, which you can also read in Xcode.

Facebooktwittergoogle_plusredditmail

Tags: ,


Leave a Reply

Your email address will not be published. Required fields are marked *