In the early days of iOS, tools to facilitate testing were not a major focus of Apple's Developer Tools team. This has gradually changed over the years with the introduction of the XCTest framework and, more recently, support for asynchronous and performance testing.
With Xcode 7 and Swift 2, Apple is taking another big leap to improve testing in Xcode. In this tutorial, I'm going to walk you through three significant additions that will make testing easier and more enjoyable.
1. Access Control
In Swift 1, developers have to jump through a number of hoops to test their code. For example, a unit test target only has access to public entities of another module. This is not surprising if you're familiar with Swift's access control. Testing internal routines, however, becomes a pain.
Apple's Swift team was aware of this problem and, as a result, Swift 2 introduces the @testable
attribute to make testing less painful. With the @testable
attribute, the unit test target has access to every internal entity of another module. The following code snippet shows how to use the @testable
attribute.
@testable import AnotherModule
Let's create a simple project to see how it works. Open Xcode 7, create a project based on the Single View Application template, and tick the checkbox labeled Include Unit Tests.
To show how the @testable
attribute works, I've implemented a structure named User
. It has two stored properties, firstName
and lastName
, and a private computed property, fullName
.
import Foundation struct User { var lastName: String = "" var firstName: String = "" var fullName: String { get { return firstName + " " + lastName } } }
Because we didn't specify an access level for the fullName
computed property, it's access level is internal. If we want to unit test fullName
, we could mark it as public. This isn't great and it defeats the purpose of Swift's access control.
Fortunately, Swift 2 solves this issue with the @testable
attribute. The following code snippet shows how the @testable
attribute is used. By prefixing the import
statement with the @testable
attribute, we can access the fullName
property of the User
structure in the testFullName
method.
import XCTest @testable import Testing class TestingTests: XCTestCase { override func setUp() { super.setUp() } override func tearDown() { super.tearDown() } func testFullName() { var me = User() me.firstName = "Bart" me.lastName = "Jacobs" XCTAssertEqual(me.fullName, "Bart Jacobs", "The full name should be equal to \"Bart Jacobs\".") } }
Note that the documentation clearly states that the @testable
attribute can only do its job if the product is compiled with testing enabled. In other words, you can't and shouldn't use the @testable
attribute for purposes other than testing.
2. Code Coverage
If you're testing the code you write, then you're on the right track. However, it's equally important to understand how well the tests you write cover the code you think they cover. While previous versions of Xcode have had support for code coverage to some extent, it has always been a bit finicky to get it to work. That's no longer the case with Xcode 7.
Instead of showing you a bunch of screenshots, I'd like to walk you through the steps involved to enable code coverage in an Xcode project. Let's take the project we created a few minutes ago as an example.
To enable code coverage, we only need to edit the active scheme. Click the scheme in the top left and choose Edit Scheme....
To enable code coverage, select Test from the left and tick the checkbox labeled Gather Code Coverage. That's it. If you've tried to enable code coverage in earlier versions of Xcode, then I'm sure you're happy to see how easy it is to enable code coverage in Xcode 7.
Run the unit tests by pressing Command-U or selecting Test from Xcode's Product menu. When the tests have finished running, open the Report Navigator on the right and select the Coverage tab at the top. This shows you how well your unit tests cover the code you've written.
It looks like we've done a pretty good job unit testing the User
structure. It gets better though. Do you see the tiny arrow following the method name? If you click it, Xcode will take you to the source code the unit test tested. Because Xcode has collected coverage data, it can also show that coverage data in the source code editor.
The coverage data is shown on the right of the source code editor. The number in the gutter on the right indicates how often the unit tests have triggered that specific routine.
Code that wasn't executed during the tests are highlighted. This is very useful for writing better tests and for improving the code coverage of your test suites.
3. UI Testing
One of the most anticipated features of Xcode 7 is the integration of user interface testing. In the past, I've worked with KIF and UI Automation, but I've never been quite happy with either solution. Xcode's UI testing looks like the solution many of us have been waiting for.
XCTest Framework
User interface testing in Xcode 7 expands on Apple's robust XCTest framework through the addition of three classes, XCUIApplication
, XCUIElement
, and XCUIElementQuery
. To make the creation of user interface tests easier, Xcode 7 provides the ability to record tests. This is a very powerful feature that some of you may already be familiar with if you've ever worked with Apple's UI Automation.
As with UI Automation, Xcode's user interface testing relies heavily on accessibility. It's therefore more important than ever to spend time making your applications more accessible to users with a disability.
Note that UI testing is only available on iOS 9 and OS X 10.11. Apple emphasizes that unit tests and user interace tests complement each other. In other words, it's not a matter of choosing one or the other.
While it's easy to create user interace tests for an application, you are not expected to write UI tests for every possible scenario or use case. Start by focusing on common and critical use cases, such as user onboarding.
Test Reports
Test reports have also improved substantially. What I like most about the new tests reports is the addition of screenshots for failed UI tests. In addition to an error message, Xcode takes a snapshot of your application's user interface when a UI test fails. This makes it very easy to find and fix the problem.
Learn More in Our Testing With Swift Course
If you want to learn more about testing with Swift and the XCTest framework, then I encourage you to take a look at Derek Jensen's course about unit testing with Swift and XCTest.
Conclusion
Apple has put a lot of effort into improving testing and debugging with the release of Xcode 7. While integrated code coverage is fantastic, the addition of user interface testing is going to have an even bigger impact.
I hope this brief overview has wet your appetite for testing in Xcode 7. We will be covering these new features in more detail in future tutorials.