Xcode Test Plans for iOS: Getting Started

In this tutorial, you’ll learn how to organize your unit and UI tests with Xcode test plans and how to test your iOS app with multiple languages/regions. By Irina Galata.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 2 of this article. Click here to view the first page.

Running the Localization Tests

Go to AccountsViewUITest and select Run in All Configurations. Do this by right-clicking the Play button to the left of AccountsViewUITest:

Select run in all configurations

Then, in the same menu, choose Jump to Report to review the test report:

Failed tests

Oh no, it failed! How can you fix it?

Start by looking at testUpdateBalance(). Expand the report for the German configuration of that test to find the cause of the failure:

Explanation of failed test

Click the Eye icon near the Automatic Screenshot text:

Screenshot of failed view

Apparently, you’re using the wrong currency symbol for the specified region: Germany. The currency appears in dollars, but it should be euros!

Adapting the Code for Locale-Dependent Tests

Go to AccountView.swift to fix the issue. You’ll find the computed property balanceString there:

private var balanceString: String {
  let amount = AmountFormatter.string(from: abs(account.balance)) ?? ""
  let balance = "$" + amount
  return account.balance < 0 ? "-" + balance : balance
}

The dollar sign is hard-coded! Hard coding, as you know, is rarely the solution, and this is no exception. :]

Update your code:

private var balanceString: String {
  // 3
  let amount = AmountFormatter.string(from: abs(account.balance)) ?? ""
  let balance = currencySymbol + amount
  return account.balance < 0 ? "-" + balance : balance
}

// 1
private var currencySymbol: String {
  // 2
  return Locale.current.currencySymbol ?? ""
}

Here's what you're doing in this code:

  1. You declare a new computed property, currencySymbol.
  2. You get the currency symbol from the current user locale. That way, the currency symbol will be for Germany and $ for the United States.
  3. Finally, you use the newly-declared property in balanceString instead of a hard-coded value.

Run the UI tests again and open the test report:

More failing UI tests

Oh! testAddAccount() still fails!

Apparently, something went wrong with the translation of the navigation title. Look into Localizable.strings (German) and you'll see that there's a translation for the title New Account:

"BudgetKeeper" = "BudgetKeeper";
"New Account" = "Neues Konto";
"Update Balance" = "Kontostand updaten";
"OK" = "OK";
"Save" = "Speichern";
"Enter the name" = "Gib den Namen ein";

What else could be wrong then? Go to CreateAccountView.swift to investigate.

OK, you set the navigation title by calling navigationBarTitle(_:). So far, so good.

But the key is wrong! It should be New Account, not New account. Account has an uppercase A, not a lowercase a. Here's how you fix this:

.navigationBarTitle("New Account")

Run the tests once again to verify the correct localization:

UI tests completed

Alles gut and way better! :]

Analyzing an App Alongside Testing

Xcode offers several tools to analyze your app as it runs, but many of them can't be used together or can affect performance. You also have to remember that the tools exist, and to turn them on (and off again), and to pay attention to the results. Good news! You can set up analysis tools as part of your test plans! Go to one of the configurations of your FullTests plan. Take a look at the settings on the bottom:

Additional options

Sanitizers

The three sanitizers will check the following things in your app while it's running:

  • Address Sanitizer (ASan) detects memory usage errors in your code. Turning on ASan can slow down your tests a bit and increase memory usage, but it's worth it to ensure your code is free of memory violations.
  • Thread sanitizer (TSan) helps you detect data races, where multiple threads access and modify a memory area in an unsafe way. These are extremely difficult to find and debug, as they are quite unpredictable.
  • Undefined Behavior Sanitizer (UBSan) detects issues like dividing by zero or accessing an array outside of its bounds.
Note: You can't turn on Address sanitizer and Thread sanitizer in the same configuration. A proper solution, in this case, would be to have one sanitizer in the German configuration and another in the English configuration.

Runtime API Checking

Main Thread Checker is on by default, and you should keep it this way for your tests. It will warn you if you use frameworks like UIKit or AppKit on a background thread. You should only update the UI in the main thread.

Memory Management

Malloc Scribble, Malloc Guard Edges and Guard Malloc help you detect usages of deallocated memory and buffer overruns. The latter means that your program overruns the bounds of a buffer while writing data into it. This results in overwriting storage adjacent to the buffer memory locations.

Note: You can't use these tools with each other or with ASan and TSan in the same configuration.

When the Zombie Objects setting is on, objects in your program won't be deallocated when they reach a retain count of 0. Instead, they'll stay half alive and half dead, just like zombies. You'll receive a warning when your program tries to access such an object.

Zombie objects

Where to Go From Here?

Download the final project using the Download Materials button at the top or bottom of this tutorial.

Well done! You've now learned how to organize your tests and how to test in multiple localizations. This means there are no more excuses for not writing those tests!

If you want to deepen your knowledge of testing iOS apps, check out our awesome iOS Test-Driven Development by Tutorials book. You'll learn about new approaches, different types of tests, how to mock external services for testing and much more.

Also, to learn more about memory management tools in Xcode, take a look at Apple's Malloc Debugging documentation.

Thank you for reading along! If you have any questions or comments, don't hesitate to leave them below.