Testing in Swift

Learn about automating the tedious yet essential task of testing a complex app in this excerpt from Learning Swift.

By Jonathon Manning, Paris Buttfield-Addison and Tim Nugent
June 2, 2016
Eye test Eye test (source: Pixabay)

Testing

While simple apps are easy to test, complex apps get very difficult to properly test. It’s simple enough to add some code and then check that it works; but the more code you add, the more you increase the chance that a change in one part of the code will break something elsewhere. In order to make sure that all of the app works, you need to test all of the app. However, this has many problems:

  • It’s tedious and boring, which means you’ll be less likely to do it thoroughly.

    Learn faster. Dig deeper. See farther.

    Join the O'Reilly online learning platform. Get a free trial today and find answers on the fly, or master something new and useful.

    Learn more
  • Because it’s repetitious, you’ll end up testing a feature in the same way every time, and you may not be paying close attention.

  • Some problems appear only if you use the app in a certain way. The more specific the use case, the less you’ll test it.

To address these problems, modern software development heavily relies on automated testing. Automated testing solves these problems immediately, by running the same tests in the same way every time, and by checking every step of the way; additionally, automated testing frees up your mental workload a lot.

There are two types of automated tests in Xcode: unit tests and user interface tests.

Unit Testing

Unit tests are small, isolated, independent tests that run to verify the behavior of a specific part of your code. Unit tests are perfect for ensuring that the output of a method you’ve written is what you expect. For example, the code that we wrote all the way back in not available to load a location from JSON is very straightforward to test: given some valid JSON containing values for lat and lon, we expect to be able to create a CLLocationCoordinates; additionally, and just as importantly, if we give it invalid JSON or JSON that doesn’t contain those values, we should expect to fail to get a coordinate.

Unit tests are placed inside a unit test bundle. You can choose to either include unit tests when you create the project, or you can add one to an existing project by opening the File menu and choosing New→Target, then opening the Tests section and choosing Unit Tests (see Figure 1).

lesw 1710
Figure 1. Adding a Unit Test bundle to a project

Test bundles contain one or more test cases; each test case is actually a subclass of XCTestCase, which itself contains the individual unit tests. A test case looks like this:

  func testDocumentTypeDetection() {

      // Create an NSFileWrapper using some empty data
      let data = NSData()
      let document = NSFileWrapper(regularFileWithContents: data)

      // Give it a name
      document.preferredFilename = "Hello.jpg"

      // It should now think that it's an image
      XCTAssertTrue(document.conformsToType(kUTTypeImage))

  }

The tests inside XCTestCase class are its methods. When Xcode runs the tests, which we’ll show in a moment, it first locates all subclasses of XCTestCase, and then finds all methods of each subclass that begin with the word test. Each test is then run: first, the test case’s setUp method is run, then the test itself, followed by the test case’s tearDown method.

You’ll notice the use of the XCTAssertTrue functions. This method is one of many XCTAssert functions, all of which test a certain condition; if it fails, the entire test fails, and Xcode moves on to the next test. You can find the entire list of XCTAssert functions in the Xcode testing documentation.

To run the unit test for your current target, press ⌘U, or click the icon at the left of the top line of a specific test, as shown in Figure 2.

lesw 1711
Figure 2. Running a specific test

Xcode will launch your app, perform the test(s), and report back on which tests, if any, failed.

UI Testing

To get a complete picture of how your app works, unit tests on their own aren’t enough. Testing a single isolated chunk of your code, while extremely useful, isn’t enough to give you confidence that the app itself, with all of its interacting components, is being tested. For example, it’s simply not feasible to write a concise unit test for “create a document, edit it, and save it.”

Instead, you can use UI tests to verify that the app is behaving the way you want it to as it’s used. A UI test is a recording of how the user interacts with the user interface; however, these recordings are done in a very clever way. While a UI test is being recorded, Xcode notes every interaction that you perform, and adds a line of code that reproduces that step.

The result is code that looks like this (we’ve added comments to describe what’s going on):

  func testCreatingSavingAndClosingDocument() {

      // Get the app
      let app = XCUIApplication()

      // Choose File->New
      let menuBarsQuery = XCUIApplication().menuBars
      menuBarsQuery.menuBarItems["File"].click()
      menuBarsQuery.menuItems["New"].click()

      // Get the new 'Untitled' window
      let untitledWindow = app.windows["Untitled"]

      // Get the main text view
      let textView = untitledWindow.childrenMatchingType(.ScrollView)
          .elementBoundByIndex(0).childrenMatchingType(.TextView).element

      // Type some text
      textView.typeText("This is a useful document that I'm testing.")

      // Save it by pressing Command-S
      textView.typeKey("s", modifierFlags:.Command)

      // The save sheet has appeared; type "Test" in it and press Return
      untitledWindow.sheets["save"].childrenMatchingType(.TextField)
          .elementBoundByIndex(0).typeText("Test\r")

      // Close the document
      app.windows["Test"].typeKey("w", modifierFlags:.Command)
  }

UI tests are run the same way as your unit tests. When they’re run, the system will take control over your computer and perform the exact steps as laid down in the test. This ensures that your app is tested in the exact same way, every time.

Tip

You can also record your interactions with an app directly into a UI test. This is extremely useful, since it means that you don’t have to learn the API involved—you can just use the app as you would normally, and Xcode will note what you did. For more information, see Writing Tests in the Xcode documentation.

Post topics: Software Engineering
Share: