© Radoslava Leseva Adams and Hristo Lesev 2016

Radoslava Leseva Adams and Hristo Lesev, Migrating to Swift from Flash and ActionScript, 10.1007/978-1-4842-1666-8_8

8. Debugging and Testing Your App

Radoslava Leseva Adams and Hristo Lesev2

(1)London, UK

(2)Kazanlak, Bulgaria

Migrating your workflow to Xcode and Swift would not be complete without an overview of the tools available to help you diagnose issues, debug and test your code.

In this chapter you will do the following:

  • See how to use Xcode’s debug gauges to track your app’s usage of CPU, energy, memory and other system resources.

  • Learn about setting up different types of breakpoints.

  • Try out debugger commands.

  • Implement a workaround for the Memory view issue we discovered in Chapter 3 .

  • See techniques for debugging concurrent code.

  • Explore Xcode’s automated testing framework and add tests to your project.

When you are done, you will have moved your workflow to Xcode and iOS with Swift and will be ready to move on to real-world app development with Part III.

Preparing the Project

In order to try the more sophisticated features of the debugger and to see how automated testing works in Xcode, we will use the app we created in the previous chapter as our guinea pig. In that app the user moves a slider to choose a number and then starts a search for all prime numbers smaller than the chosen number (Figure 8-1) . In Chapter 7 we implemented a couple of different ways of structuring the search: with and without concurrency.

A371202_1_En_8_Fig1_HTML.jpg
Figure 8-1. The FindPrimeNumbers app

To prepare for the tutorials in this chapter, make a backup copy of the FindPrimeNumbers project from Chapter 7 and then open FindPrimeNumbers.xcodeproj in Xcode.

Making the Best of the Debugger

If by now Xcode is beginning to seem less like a Swiss Army knife and more like a space shuttle, you are not far from the truth. It is equipped with so many gadgets you may not need to leave it for weeks, and you may never reach for a tool outside it. While the last statement may or may not be true in your case, it is worth getting to know the shuttle’s equipment and the various ways it can help you write better code. In this part of the chapter we look at some useful debugger features and techniques you can use to diagnose issues in your apps.

Inspecting the Debug Gauges

We will first run the FindPrimeNumbers project without making any changes to it and have a look at the Debug navigator. At the top there is a list of debug gauges , which show you how the state of system resources changes over time as your app is running. You can inspect processor load (CPU), memory usage, energy impact, disk usage, traffic over the network, iCloud, and graphics processing unit (GPU) usage.

For example, when you initially run the app, it hardly uses any CPU time at all. But set it up to do a lengthy prime number search by moving the search slider up and you will see some impact on the CPU (Figure 8-2).

A371202_1_En_8_Fig2_HTML.jpg
Figure 8-2. Show or hide the debug gauges at the top of the Debug navigator

When you select a gauge, the Editor changes to show you a detailed report of the respective system resource (Figure 8-3).

A371202_1_En_8_Fig3_HTML.jpg
Figure 8-3. Select a gauge to see a detailed report in the Editor

Note how, for some gauges , a button appears at the top left of the Editor, called Profile in Instruments. Clicking it prepares your project for profiling and launches Xcode’s instruments, which include profilers for time, memory, and network usage among other things. We had a look at these in Chapter 4 .

Setting up breakpoints

Back in Chapter 3 we learned how to add breakpoints in the Editor. Let us now see what control we have over them.

You can add breakpoints not only at lines of code in your source files but also breakpoints that will be triggered by events, such as errors or exceptions. You can also add breakpoints for calls into the SDK or third-party libraries, for which you may not have the source code: these are set up as symbolic breakpoints.

Open the Breakpoint navigator and click the + button at the bottom to see the kinds of breakpoints that are available (Figure 8-4).

A371202_1_En_8_Fig4_HTML.jpg
Figure 8-4. Adding a breakpoint in the Breakpoint navigator

Let us add a symbolic breakpoint to see how these work. Select Add Symbolic Breakpoint... from the pop-up menu you saw in Figure 8-4. We will have the debugger stop inside the isItPrime method we implemented in the ViewController class, but only after it has been called 100 times. In the setup dialog set Symbol to isItPrime and Ignore to 100 times before stopping.

Apart from determining when the breakpoint will be hit, we can also set up actions to be triggered when it happens. Let us set up two actions: one will play a sound to alert us when the debugger stops at the breakpoint and the other one will execute a command. Click the Add Action button next to Action. Set Action to Sound and choose one of the available sound effects (we like the frog . . .) Then click the + button on the right of the sound action you just added, in order to add another one. Select Debugger Command as the action type and in the text field underneath put the following command:

expr "Checking if (number) is prime"

Figure 8-5 shows how the breakpoint is set up.

A371202_1_En_8_Fig5_HTML.jpg
Figure 8-5. Setting up a symbolic breakpoint

Run the app through the debugger and when it loads, tap the Start button. After a few moments there should be a (slightly shy sounding) frog call and you should be able to see the result of the command in Xcode’s console (Figure 8-6).

A371202_1_En_8_Fig6_HTML.jpg
Figure 8-6. Executing a debugger command when a breakpoint is hit

What we just did was to execute a debug command in Xcode’s console: expr is short for expression, which we used in Chapter 3 to change values at runtime. We will have another look at debugger commands next.

Communicating with the Debugger

Xcode uses the LLDB debugger and, in addition to controlling it from the user interface, it gives us the option to talk to it by running commands in the console. The format of an LLDB command is:

command options -- arguments

The double dash (--) serves as a divider between any options and arguments you may pass to the command.

Let us try a few commands. With the debugger paused at the breakpoint we defined earlier type this in the console:

e self

This command uses an abbreviation: e is another short form of expression. You can see the result in the console: expression infers the meaning of self from the context, in which we have paused the debugger, and prints out a hierarchical description of the instance of ViewController we are using (Figure 8-7).

A371202_1_En_8_Fig7_HTML.jpg
Figure 8-7. Printing out a description of ViewController’s instance

Let us try another one:

e –O -- self

Executing this evaluates self again, this time only displaying a top-level description of the ViewController object without its members: <FindPrimeNumbers.ViewController: 0x15d940d0>.

The last version of the command we ran is equivalent to

po self

Here po is a shortcut for e -O (the command expression, run with the option –O). In fact, the print command we used in Chapter 3 to print out values of objects is also a variation of the same command and is equivalent to expression --.

Here are a couple more commands for you to try when the debugger stops at the breakpoint in isItPrime:

finish

The finish command is a shortcut for thread step-out. It completes the running of the current function (stack frame) and goes back to its caller. When you run it, you will see execution go to findPrimeNumbersInRange and pause there.

continue

This command is short for process continue and instructs the debugger to proceed with the execution until another breakpoint is hit.

You can, of course, use the debugger user interface (UI) to control execution, as shown in Figure 8-8.

A371202_1_En_8_Fig8_HTML.jpg
Figure 8-8. Using the debugger controls

Finally, if you remember only one LLDB command, make it this one:

help

As its name suggests, help will give you a hand with any other command you may want to learn about. Running help will print out a list of all available commands and help command-name will give you details about the options and syntax for a particular command.

Checking the Type of a Symbol

In the previous chapters we mentioned a few times and you have seen in examples that you do not always need to specify a type when you declare a symbol (a constant, a variable, a function, etc.) The Swift compiler is good at inferring type from the context, in which a symbol is declared (see Chapter 17 for details). The code that you end up with if you omit all type declarations wherever possible can be very concise, but also a bit harder to read. It can also leave you wondering about what type a symbol is.

There are a couple of ways to reveal the type of a symbol at runtime while the debugger is paused at a breakpoint. You can option-click the symbol in the Editor to see its declaration as shown in Figure 8-9.

A371202_1_En_8_Fig9_HTML.jpg
Figure 8-9. Using Alt + click to see the declaration of a symbol

You can also toggle symbol types in the Variables view: right-click inside the view and from the pop-up menu select Show Types (Figure 8-10).

A371202_1_En_8_Fig10_HTML.jpg
Figure 8-10. Showing symbol types in the Variables view
Tip

Before we move on it may be a good idea to disable the breakpoint we set for isItPrime. To do that, find the breakpoint in the Breakpoint navigator, right-click it, and select Disable breakpoint. This will leave the breakpoint in, in case you want to switch it on again by selecting Enable breakpoint. Note that you can also select Delete breakpoint when you want to remove a breakpoint permanently.

Inspecting Memory—A Workaround

We have not forgotten our promise to show you how to inspect the memory of a symbol, in case the straightforward way we demonstrated in Chapter 3 does not work. We will see how to deal with this scenario here.

First, let us set a breakpoint on this line in the displayResults method:

resultsTextView.text = resultStr

We want to see the memory of resultStr. Run the app through the debugger and when the breakpoint is hit, find resultStr in the Variables view. Right-click it and from the pop-up menu select View Memory of “resultStr” (Figure 8-11).

A371202_1_En_8_Fig11_HTML.jpg
Figure 8-11. Selecting a variable in the Variables view to inspect in the Memory view

If you remember back from Chapter 3 , what we expect to see is the Memory view with the memory footprint of the variable. However, what you are likely seeing instead is this: the screenshot in Figure 8-12 shows the memory for address zero.

A371202_1_En_8_Fig12_HTML.jpg
Figure 8-12. Inspecting the memory for a variable someitimes results in showing address 0x0

This is a known issue, which Apple describes in the Xcode 6 Release Notes ( https://goo.gl/Pki8UV ):

View Memory for Swift data structures in the debugger may show memory location zero.

The following workaround is suggested:

Pass the structure to a function expecting a reference to an UnsafePointer<Void>, and print it within the function. Enter this address as the memory location to view.

Xcode is ever-evolving and helping make Swift development easier, so this issue will probably be fixed in a release very soon. Since it is still present in Xcode 7, however, let us see how to implement the suggested workaround.

Add the following method to the ViewController class (Listing 8-1):

Listing 8-1. Printing Out the Address of a Symbol
// MARK: Debugging helper methods
func printAddressOf(symboToInspect: UnsafePointer<Void>) {
    print("Actual address in memory: (symboToInspect)")
}
Swift reference

Pointers are part of Swift, in order to make it compatible and allow it to interact with C and Objective-C code. For the most part you will rarely need to use pointers, but it is worth being aware of how they work. A pointer is an object, which keeps the memory address of another object and thus “points” to it. Using pointers can save both time and memory when large objects have to be passed around. It is much more expensive to copy an object and all its data than to copy its address and dereference it (i.e., find the object by its address). There are risks when using pointers, however. For example, a pointer can hold an address of an object, which was deallocated or can point to memory that has not been initialized yet. The name of Swift’s UnsafePointer type signals just that: this type of pointer does not automatically change when changes happen at the memory location, to which it points.

Let us add a call to printAddressOf just after we have finished composing resultStr in the displayResults method as shown in Listing 8-2.

Listing 8-2. Adding a Call to printAddressOf
func displayResults(primes: [UInt]) {
    if primes.isEmpty {
        resultsTextView.text = "No prime numbers were found."
        return
    }


    // Iterate through the primes array and construct a string,
    // which will be displayed in the text view:
    var resultStr = "(primes.count) prime numbers were found: "
    for i in primes.indices {
        resultStr += "Prime [(i + 1)] = (primes[i]) "
    }


    // Workaround for the Memory view issue:
    printAddressOf(resultStr)


    resultsTextView.text = resultStr

    // The search has finished, now disable the Cancel button:
    cancelButton.enabled = false
}

Now, with the breakpoint we added earlier still in place, run the app through the debugger, and tap the Start button. When the breakpoint is hit, have a look at the console: there you should see the result of the print statement we put in the body of printAddressOf:

Actual address in memory: 0x15bace14

The hexadecimal number you see is the memory location (address) of the resultStr variable.1 We will copy and paste it in the Address field in the Memory view as shown in Figure 8-13. Hit Enter and you should now see the memory footprint of the variable: it is shown as hexadecimal numbers on the left and as string in the right part of the Memory view.

A371202_1_En_8_Fig13_HTML.jpg
Figure 8-13. Manually entering an address to inspect in the Memory View
Tip

Now that you have implemented the official workaround, here are a couple of quicker ways to get the address of a symbol, which don’t require additional code.

With a breakpoint active, you can print out the address in the console by running the following command:

po resultStr._core._baseAddress

Alternatively, you can expand resultStr in the Variables view, right-click its _baseAddress and from the menu select Print Description of _baseAddress. This too will print the address in the console, which you can then copy and paste in the Memory view.

Debugging Concurrent Code

In Chapter 7 we implemented a method, called concurrentlyFindPrimeNumbersLessThan, to look for prime numbers smaller than a given number. It keeps the UI responsive by creating a new custom concurrent queue, which is separate from the main execution queue, and using it to execute tasks to search for prime numbers. (Have a look at Chapter 7 for a reminder of how queues work on iOS.) Each concurrent task searches for prime numbers in a portion of the range between zero and the given number and collects its results in an array. When a task completes, it adds those results to another array, which accumulates the prime numbers found by all tasks. In order to restrict access to this array to one task at a time, we put a dispatch barrier around the code, which accesses it.

To see how we can inspect concurrent code, we will do something naughty. You probably remember when we discussed deadlocks in Chapter 7 : when two or more tasks each wait for one another to finish, in order to continue execution, they are in a deadlock. We will introduce one to see how we can detangle it—useful to know when your app hangs, for example.

Let us make a few changes to the code, in order to artificially create a deadlock situation . Listing 8-3 shows the new implementation of the concurrentlyFindPrimeNumbersLessThan method.

Listing 8-3. Modifying concurrentlyFindPrimeNumbersLessThan, So We Create a Deadlock
func concurrentlyFindPrimeNumbersLessThan(number limit: UInt) {
    // Make sure we clear the blocks array from any old task references:
    blocks.removeAll()


    // Let us split the search into four:
    let concurrentTaskCount: UInt = 4


    // Based on the numer of concurrent tasks we want,
    // calculate the size of the interval
    // each task will need to search through:
    let rangeSize = limit / concurrentTaskCount + limit % concurrentTaskCount


    // Keep track of how many tasks have completed:
    var tasksDone: UInt = 0


    // An array to combine results in the end:
    var primes = [UInt]()


    // Create a custom serial queue:
    let queue =
        dispatch_queue_create("com.diadraw.primeNumberSearchQueue",
            DISPATCH_QUEUE_SERIAL);


    for i in 0 ..< concurrentTaskCount {
        // Work out the interval for the search:
        let rangeStart = i * rangeSize
        if rangeStart >= limit {
            // If we have already covered the whole interval,
            // mark the task as complete:
            tasksDone += 1
            continue
        }
        let rangeEnd = min(UInt(rangeStart + rangeSize - 1), limit - 1)


        // Define a block of code to perform the search:
        let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
            // Tasks have now started executing and we cannot cancel them,
            // so disable the Cancel button:
            dispatch_async(dispatch_get_main_queue(), {
                self.cancelButton.enabled = false
            })


            let primesInCurrentRange =
                self.findPrimeNumbersInRange(range: rangeStart ... rangeEnd)


            // Perform the writing to the primes array synchronously,
            // so that no more than one task can write to it
            // at the same time:
            dispatch_sync(queue, {
                primes.appendContentsOf(primesInCurrentRange)


                // Keep track of how many tasks have completed:
                tasksDone += 1


                // When we've got the last chunk of results in, display them:
                if tasksDone == concurrentTaskCount {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.displayResults(resultsArray: primes)
                    })
                }
            })
        }


        // Enqueue the block to be executed asynchronously:
        dispatch_async(queue, block)
    }
}

Let us walk through the changes we made and explain them. First, we replaced the custom concurrent queue with a serial one:

let queue =
    dispatch_queue_create("com.diadraw.primeNumberSearchQueue",     
        DISPATCH_QUEUE_SERIAL)

Then, instead of putting the code, which writes to the results collection in a dispatch barrier (dispatch_barrier_async), we just dispatch it to the queue in a synchronous way:

dispatch_sync(queue, { /*Collect results and display them on the screen.*/ })

In the original implementation, at the end of the for-in loop, we used to add tasks to the queue after a delay. This time we enqueue each task immediately with dispatch_async for simplicity:

dispatch_async(queue, block)

Let us run the app and tap the Start button. Can you see what happens? Probably not, as nothing seems to be happening. To find out what is going on, we will pause the execution. Click the Pause button in the Debug area as shown in Figure 8-14.

A371202_1_En_8_Fig14_HTML.jpg
Figure 8-14. Pausing the debugger

This immediately changes the view to show the Debug navigator, as if we have hit a breakpoint. Let us now have a look at the Debug navigator: underneath the debug gauges there are stack traces, listed by thread. Each stack trace shows what code a particular thread was running when we paused the execution. Use the button at the top right-hand corner to switch to viewing queues (select View Process by Queue from the pop-up menu) as shown in Figure 8-15.

A371202_1_En_8_Fig15_HTML.jpg
Figure 8-15. Switch the debug view to show dispatch queues

Inspecting queues takes us to more familiar territory. In the list of queues we can see the one we created, listed with its name, its type, and the number of code blocks currently in it:

com.diadraw.primeNumberSearchQueue (serial) 1 Running Block, 3 Pending Blocks

Let us expand the queue entry and see the call stack for each of the blocks. At the top there is a call to semaphore_wait_trap. The cogwheel icon next to it means that this is a system call.

Tip

For a full list of debugger icons, see Debugging with Xcode in Apple’s online documentation at https://goo.gl/3cgkSt .

In order to understand what semaphore_wait_trap means, let us explain what semaphoresare. This is a technique for controlling access to common resources from multiple threads or processes. Say we have a block of code and want to ensure that only one thread at a time can execute it, such as when we write results to the primes array in the concurrentlyFindPrimeNumbersLessThan method. A semaphore could be a Boolean flag, which signals true if the code is available for running. When a thread begins executing the block, the semaphore flag is set to false and any other thread that needs to execute the same block of code will be suspended and wait until the flag becomes true again. The call to semaphore_wait_trap at the top of the stack means that the thread, on which the custom queue has placed our block of code, is currently suspended and waiting for a semaphore to signal that it is allowed to continue execution. For information on threads and queues look back at Chapter 7 .

If we look down the call stack , shown in Figure 8-16, we can find where in our code the execution has been suspended. Calls that are made into our (user) code appear with a blue icon with a person outline on it. The topmost one we can see reads

A371202_1_En_8_Fig16_HTML.jpg
Figure 8-16. Inspecting the call stack
ViewController.(concurrentlyFindPrimeNumbersLessThan(UInt) -> ()).(closure #1)

If you click on this call, you will be taken to a line of code in the Editor (Figure 8-17). This line of code happens to be at the end of the block of code we enqueued synchronously on our serial queue. We did that in order to make sure that only one task can modify the primes array at a time and write its results to it.

A371202_1_En_8_Fig17_HTML.jpg
Figure 8-17. Going from the call stack to a particular line of code

Looking further down the call stack we can see more blocks of code, which have been placed on the same queue. Do you see how the icon next to each call in these blocks has been grayed out? This is because the blocks are pending execution (i.e., they are not running at the moment). In Figure 8-18 we can see four blocks of code, one of which contains another, nested, block.

A371202_1_En_8_Fig18_HTML.jpg
Figure 8-18. Inspecting blocks of code on the call stack

It is only the nested block (marked as “Inner running block” in Figure 8-18) that is active. This is the block we matched to the dispatch_sync call in concurrentlyFindPrimeNumbersLessThan. Listing 8-4 shows it in context.

Listing 8-4. The Block of Code, Which Is Currently Running
func concurrentlyFindPrimeNumbersLessThan(limit: UInt) {
    // ...


    for i in 0 ..< concurrentTaskCount {
        // ...


            // Perform the writing to the primes array synchronously,
            // so that no more than one task can write to it
            // at the same time:
            dispatch_sync(queue, {
                primes.appendContentsOf(primesInCurrentRange)


                // Keep track of how many tasks have completed:
                tasksDone += 1


                // When we've got the last chunk of results in, display them:
                if tasksDone == concurrentTaskCount {
                    dispatch_async(dispatch_get_main_queue(), {
                        self.displayResults(primes)
                    })
                }
            })
        }


        // ...    
    }
}

This block of code is nested inside the definition of the block constant: another code block, which we later add to the same queue by calling dispatch_async (Listing 8-5).

Listing 8-5. The Outer Block of Code, Which Is Pending Execution
func concurrentlyFindPrimeNumbersLessThan(limit: UInt) {
    // ...    


    for i in 0 ..< concurrentTaskCount {
        // ...


        // Define a block of code to perform the search:
        let block = dispatch_block_create(DISPATCH_BLOCK_INHERIT_QOS_CLASS) {
            // The definition of the block,
            // containing the nested call to dispatch_sync from Listing 8-4
        }


        // Enqueue the block to be executed asynchronously:
        dispatch_async(queue, block)
    }
}

If you click the top user call in the grayed-out part of the call stack as shown in Figure 8-19, it will take you to that same line.

A371202_1_En_8_Fig19_HTML.jpg
Figure 8-19. Finding out where the block of code is waiting
dispatch_async(queue, block)

Here is what has happened. We create the first block of code and dispatch it asynchronously on our custom serial queue. The block starts executing and gets to the inner block of code, which it tries to dispatch to the same queue synchronously. Before that inner block of code can start execution, however, it needs to wait for the block, currently executing, to finish. But for that to happen, the inner block of code must be executed. Thus the two blocks of code end up in a deadlock.

Diagnosing deadlocks, race conditions, and other conflicts in concurrent code is not easy. Finding a call to semaphore_wait_trap is not necessarily an indication of a deadlock. Instead, the waiting block of code might just temporarily have ended up there until the resource it needs becomes available. However, it is a good place to start and if you find your app spending a long time in such calls, a deadlock could be a primary suspect.

Testing Your Code

Testing is an important part of any development workflow and does not necessarily have to happen at the end of a development cycle. In fact, proponents of Test-Driven Development (TDD) like Kent Beck would urge you to “[not] write a line of new code unless you first have a failing automated test.”2 Coming up with a test strategy and designing tests before you have even written the code has important benefits .

  • It forces you to consider real-world usage and corner cases in advance.

  • Tests can document your intentions for the code you are about to write.

  • Regularly running automated tests can help with catching issues with newly checked-in code early and save development time.

  • The tests you design might even uncover bugs! It is important to keep in mind the old principle that no amount of testing can prove your code bug-free. Testing can only prove bugs’ existence.

There is always a trade-off between the amount of time invested in designing and putting test strategies in practice and the time it takes you to maintain your code without testing.3 It would be hard for an integrated development environment (IDE) to offer automated tools for any test imaginable. For example, user experience is best evaluated through . . . well, letting users experience your app and give you feedback. What Xcode has to offer via its XCTest framework, however, makes certain types of tests so easy to automatically integrate in your projects that it would be inexcusable not to put them to use. The types of tests you can include are

  • Unit tests. Apple divides these into functional unit tests and performance tests. Functional unit tests exercise part of your code by running it and comparing the output to an expected result—that is, they treat code as a black box. Performance tests help you measure how long it takes your code to perform selected tasks and keep track of whether performance changes over time.

  • User interface tests. Xcode lets you record a sequence of actions that a user would perform in the UI and define the expected state of the app after these actions. The sequence can then be replayed automatically, in order to check if the app is in the expected state.

We will add two unit tests: a functional one and a performance one to the same project we have been abusing in the previous sections of this chapter.

Tests are usually created in a separate project target. If you ticked the Include Unit Tests box when you created the project for Chapter 7 , a test target has already been created for you by Xcode. To check if this is the case, in Project navigator select the main project and see if FindPrimeNumbersTests appears under Targets in the Editor. You should also be able to see FindPrimeNumbersTests in the project tree as shown later on in Figure 8-21.

If there is no test target in the project, let us create one. With the FindPrimeNumbers project open in Xcode select FileTarget... Then, at the first step of the wizard select iOSTestiOS Unit Testing Bundle (Figure 8-20).

A371202_1_En_8_Fig20_HTML.jpg
Figure 8-20. Adding a unit testing bundle to your project

At the next step the wizard offers to name the new target after the project with Tests appended to the name (i.e., FindPrimeNumbersTests). Accept that, as well as the rest of the settings that have been set by default, then click Finish.

The new target is added to the project with its own Info.plist settings file and a source file, called FindPrimeNumbersTests.swift (Figure 8-21).

A371202_1_En_8_Fig21_HTML.jpg
Figure 8-21. The test target in the Project navigator

Inside FindPrimeNumbersTests.swift you will find the definition of a class, called FindPrimeNumbersTests. It is a subclass of XCTestCase from the XCTest framework in the iOS SDK.

This is what FindPrimeNumbersTestslooks like out of the box (Listing 8-6).

Listing 8-6. FindPrimeNumbersTests, Defined by Xcode
class FindPrimeNumbersTests: XCTestCase {

    override func setUp() {
        super.setUp()
    }


    override func tearDown() {
        super.tearDown()
    }


    func testExample() {
    }


    func testPerformanceExample() {
        self.measureBlock {
        }
    }
}

The testExample and testPerformanceExample methods are where we add test code. The other two methods, setUp and tearDown, are called before and after each of the test methods are run and we can use them to do initialization and cleanup.

You can add as many test methods as you need to. As a general rule, make each method test one thing and one thing only and keep related tests together (i.e., as methods of the same XCTestCase subclass).

Before we add our first test, let us see how the test code can access the code in the app target. In Swift, access controlis defined in terms of source files and modules. A module is a collection of source files that are built and shipped together as an application or as a framework. By default, only declarations, which are made public, are visible (i.e., accessible) to code outside the module where they have been defined (more details on that in Chapter 17 and Chapter 21 ). This means that the test module we added does not see the source code in our app module by default. In order to give it access to them, we need to import the app module, FindPrimeNumbers, in our test code with the @testable keyword. Open FindPrimeNumbersTests.swift and add the following line at the top of the file under import XCTest:

@testable import FindPrimeNumbers

This line does two things: it lets the test code know about the app code but also gives it access even to entities in FindPrimeNumbers, which have been declared private.

Next, we will add an instance of ViewController as a property to the FindPrimeNumbersTests class and initialize it inside the setUp method (Listing 8-7). Note that this will create a new instance of the class every time we run a test. Although this is not too much of a hassle for our simple scenario, it is good to keep in mind. Depending on what you test, you may or may not want to start with a fresh instance before each test case is run.

Listing 8-7. Adding an Instance of ViewController
var viewController: ViewController!

override func setUp() {
    super.setUp()


    let storyboard = UIStoryboard(name: "Main", bundle: NSBundle.mainBundle())
    viewController =
        storyboard.instantiateInitialViewController() as! ViewController
}

Adding a Functional Unit Test

Let us now add a test. Replace the testExample method in FindPrimeNumbersTests with the method from Listing 8-8.

Listing 8-8. Adding a Test
func testIsItPrime_0() {
    let result = viewController.isItPrime(number: 0)
    XCTAssertFalse(result)
}

testIsItPrime_0 tests the isItPrime method of ViewController by treating it like a black box. It calls isItPrime with zero as the parameter and expects to receive a negative result. The result is checked in the XCTAssertFalse call. If the argument of XCTAssertFalse evaluates to false, the test passes, otherwise the test fails.

Swift reference

The XCTAssertFalse call in Listing 8-8 uses an assertion: a programming technique that helps you state and check assumptions about the correctness of your code.

In general, an assertion takes a Boolean argument and does nothing if this argument evaluates to true, but in the case of false it alerts you by outputting a diagnostic message and throwing an error or even terminating the app. Using assertions helps you define clear responsibilities for each function you implement. Say that a function relies on a certain condition to be true, in order to do its job—for example, a variable having a value and not being nil. Instead of silently handling the case when the variable is nil and allowing it to cause issues down the line, the function can assert for the condition and alert you sooner and closer to the cause of the trouble. It is important to note that assertions are a debugging technique and are disabled (i.e., do nothing) when your app is built for release. This means that your diagnostic code can be left in without bloating the final product. Remember, assertions are a diagnostic tool rather than a tool for error handling.

To use assertions in your functional code, call the global assert function, like we did in findPrimeNumbersLessThan in the ViewController class.

When writing tests, you have a whole arsenal of convenience assertions at your disposal: they are part of the XCTest framework and are prefixed with XCT. We just used XCTAssertFalse, which, in contrast to traditional assert statements, expects its argument to evaluate to false. You can also use XCTAssertTrue, the more general XCTAssert, XCTAssertEqual, and so on. For a full list have a look at the XCTTest API (application programming interface).

The Test navigator offers you a dashboard for running and inspecting the results of your tests. New tests normally appear in the navigator as you add them in the code and save your changes. You can also make sure that the list in the navigator is up to date by running a build from Xcode’s main menu: ProductBuild ForTesting. As Figure 8-22 shows, each test appears with a button next to it, which lets you run it separately from the rest of the suite.

A371202_1_En_8_Fig22_HTML.jpg
Figure 8-22. Tests appear in the Test navigator

When you click the button, Xcode builds the project, installs it on your device (or in a simulator), and runs the test. You will see your app loading and then quickly disappearing, as this test does not actually take very long.

If the test passes, a green tick appears next to its name in the Editor and in the Test navigator (Figure 8-23).

A371202_1_En_8_Fig23_HTML.jpg
Figure 8-23. A test is marked with a green icon when it has passed

You can add more tests as a homework exercise: for example, make sure you run isItPrime with other interesting cases: 1, 2, UInt.max, a large prime number . . .

Adding a Performance Test

In order to see how performance tests work, we will replace testPerformanceExample with our own test, called testPerformanceFindPrimeNumbersInRange. You can see its implementation in Listing 8-9. There is an interesting call to note there: self.measureBlock is a method of the base XCTestCase class, which measures the performance of the code block inside the curly brackets that follow it. In our test we will measure how long it takes ViewController’s findPrimeNumbersInRange method to find the prime numbers between 0 and 1000.

Listing 8-9. Adding a Performance Test
func testPerformanceFindPrimeNumbersInRange() {
    self.measureBlock {
        self.viewController.findPrimeNumbersInRange(range: 0...1000)
    }
}

When you run the test, you will again see your app appearing on the device or in the simulator and then disappearing after the test has run. Xcode actually executes the test ten times in a row and calculates the average running time and the standard deviation for those runs.

Results are conveniently reported in the Editor next to the test declaration (Figure 8-24). When you click the results, a pop-up dialog shows you a more visual representation. The first time you run the test, there is no baseline to compare the results to, and so you are prompted to set it. Once set, it will be used as a criteria for the test passing: this way you will be alerted if the code you are testing suddenly becomes much slower as the project evolves.

A371202_1_En_8_Fig24_HTML.jpg
Figure 8-24. Displaying performance test results

Tracking Code Coverage

In addition to automating part of your testing process, Xcode can help you keep track of how much of your code is actually exercised by automated tests. To enable this from the main menu select ProductSchemeEdit Scheme and then open the Test scheme. Enable Gather coverage data next to Code Coverage (Figure 8-25).

A371202_1_En_8_Fig25_HTML.jpg
Figure 8-25. Enabling Xcode to gather code coverage data

After you have run a test, open the Report navigator, select the test report, and then go to the Coverage pane in the Editor. This will show you visually how much of the code is currently covered by tests (Figure 8-26).

A371202_1_En_8_Fig26_HTML.jpg
Figure 8-26. Displaying performance test results

As you can see, the coverage for FindPrimeNumbers can be improved quite a lot. We hope that you would enjoy doing this as homework.

Diagnosing Release Builds

It is all very well to rely on the debugger and make the most of automated testing while your app is in development. Once it is in the real world, however, your job for maintaining it and fixing issues on time becomes a bit harder, as you rely on user feedback. You will probably not be surprised that Apple has provided tools and services for that too. Apple makes it easy for users to report issues and for you to track and fix them. We will close this chapter with a few words on a couple of tools that help you maintain your release builds.

  • TestFlight. This is a service, available to members of the iOS Developer Program. It makes it very easy for beta testers to install your app on their devices and send you feedback, crash logs, and usage statistics.

  • The Organizer window. Once you have published your apps in the App Store, you can receive crash reports in Xcode’s WindowOrganizerCrashes. These are symbolicated for you by Xcode—that is, the addresses from the crash report are linked with lines of code to allow you to inspect and diagnose issues.

Summary

This is the final chapter in Part II of this book, which helps you port your workflow and start making the most of what Xcode has to offer to iOS developers. Here we covered some powerful features of the debugger and ways they can help you keep an eye on how your app utilizes system resources, step through your code with sophisticated breakpoints, and detangle concurrent code issues. We also had a look at Xcode’s automated test frameworks and how they can be a valuable asset to your workflow.

You are now ready to move on to the more exciting Part III, where we will build some fun apps. The first one will allow users to send e-mail and text messages, as well as make phone calls from within the app.

Footnotes

1 Note that the actual address in memory will most likely be different on your device and may change between runs of the application.

2 Kent Beck, Test-Driven Development by Example (Boston: Addison Wesley, 2002).

3 As testing consultant and speaker James Lyndsay puts it: “An organisation's greatest investment in testing with any tool is likely to be the cost of understanding the tool and developing plus maintaining the tests.”

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset