iOS

Better and cleaner code: Unit testing with Xcode

Add unit tests while developing your app to ensure your app works as expected and fixes remain fixed as you make changes.

Here is an example of adding a feature to an app and a unit test case at the same time. We will be adding the feature to Apple's Unit Test sample to show how easy this is to do.

The OCUnit test unit framework integrated into Xcode divides unit tests into application and logic tests. Logic tests exercise smaller units of code and only run in the iOS simulator. Application tests run in the context of the application. In practical terms, this means view controller methods are exercised. Application tests run in the simulator and on the device.

We will focus here on application tests. All code should be tested, but for now we will take a top-down approach and start with the application inputs and outputs. This is the best approach to take when adding unit tests to an app in the middle or end of its development cycle.

Unit testing apps and frameworks

The first step is to download the Unit Testing sample from the Apple Developer site.

Move the UnitTests.zip from the Downloads folder and unzip. Go to the iOS_Calc folder below UnitTests and open the Xcode iOS_Calc project. After opening the project, you may see a few warnings. These are easily fixed (e.g., create the default 568H retina launch image).

The iOS_Calc app is a simple calculator. The logic tests exercise the calculations and the application tests exercise the user pressing the calculator buttons. An unintentional side effect is the keyboard appearing if you tap on the field that displays the calculations. A serious app would disable keyboard, but we will make it possible to use the keyboard to enter calculations. This is somewhat contrived because the keyboard is not as usable as the buttons, but here we are just demonstrating unit testing not an iPhone calculator.

CalcView.xib

We can make the default keyboard a little more usable. Select the CalcView.xib file in the Project Navigator so it shows up in the Xib editor. Select the Attributes inspector and update the keyboard properties as follows: Capitalizations: None, Corrections: No, Keyboard: Numbers and Punctuation.

UITextFieldDelegate

The calculator display field is a UITextField. Let's set the field's UITextFieldDelegate to the owner of the Xib file, the CalcViewController view controller. With CalcView.xib displayed in the Xib editor, select the Connections inspector. Drag a connection from the delegate on the left to the File's owner placeholder on the right.

We are done with the CalcView Xib. We will now implement UITextFieldDelegate protocol methods. Select iOS_CalcViewController.m in the Project Navigator so CalcViewController is displayed in the editor.

First, as a formality, extend the CalcViewController's interface by declaring that it implements the UITextFieldDelegate protocol.

We implement two UITextField delegate methods, shouldChangeCharactersInRange and textFieldShouldReturn.

The shouldChangeCharactersInRange method is called for each character the user enters into the display field and allows the developer to determine whether the character should be displayed. For our method we will always return NO, because we want to display only the result of inputting that character into the calculator.

Before we set the calculator's input we need to make sure the character is valid. The calculator only accepts digits 0 through 9, the decimal place, add, subtract, divide, and multiply signs, and C for clear, and D for delete. First we check if the user tapped the delete key. If so, the string is empty, so we set the string to "D."

After checking that string contains a character the calculator accepts with rangeOfCharacterFromSet, we the set the calculator input to the string and set the display field to the calculator's output display value.

The textFieldShouldReturn method is implemented to dismiss the keyboard when the user taps on the done key.

The Unit Test

We test by adding a method with a name starting with "test" to the iOS_CalcTests class. The framework automatically calls the setup method before running our test case. In setup, CalcViewController is obtained from the application delegate.

CalcViewController doesn't have a public method to test entering calculations directly into the display field with the keyboard. But because we wired the UITextField delegate to CalcViewController in the XIB, we can test by calling the delegate's shouldChangeCharactersInRange method directly. We get the delegate from the view controller's display field and simulate the user tapping on the keyboard by calling shouldChangeCharactersInRange one character at a time.

If at the end of the calculations STAssertTrue macro fails the framework sends a failure message. Failures are displayed in the output console and the issue and log navigators. The unit test result macro reference is available in the iOS Developer Library.

If there is a problem with your application test, you can set a breakpoint to step through it. However, I found that breakpoints work only when running the test in the simulator.

Run

Now it is time to run the application unit tests. There are two ways to run the tests. The easiest is to select Test from the Xcode Product Menu. The other way is to alt-click the Run button in the upper left hand corner. In the Run dialog that appears select Test from the left-hand configuration column and click the Test button at the bottom.

The Xcode Unit Testing Guide claims that application tests allow you to test that your Xib and storyboard connections remain in place as you develop your app. I did not find this to be true. View controller methods can be called directly, but only a simulation of the user tapping buttons and keyboard keys exercises the connections. For that you need the UI Automation instrument.

More iOS Unit Test Frameworks

Conclusion

It is important to develop unit tests as you develop your app. It should be okay, however, to write the code first and then write the tests. But make sure all code paths are covered. You might discover that the test cannot be written without re-writing the code. But after re-writing the code it should be cleaner. This is Test Driven Development in spirit, because the objective is cleaner code.

Also read:

About

Gerald McCobb first worked on mobile phone technology in 1998 while at IBM. Since then he has been the IBM representative to the W3 multimodal working group and Technical Director of multimodal technology at Openstream, Inc. He is currently working...

0 comments

Editor's Picks