Posts tagged ‘tutorial

Testing in iOS

Since doing some programming in a TDD format, I realised the lack of documentation about testing in iOS. A number of the testing documents on the Apple Developer site have now vanished and redirect to “Real-World Testing”, which is not desired. The community behind Ruby (on Rails) seems to be heavily biased towards writing tests and testing a relatively large percentage of code coverage, if not all of it. Switching from a number of heavily tested projects in Rails to a new iOS application, I thought testing should be a large part of it to make sure everything was working.

Test-Driven Development

Test-Driven Development (TDD) is a way of developing websites/applications by writing the most basic of tests, and making them pass with your code. By only writing enough code to past the tests, and nothing more, the coverage of tests can be endless. You will have the knowledge that almost anything you break will get tested and show up in red.

When developing Rails apps, I usually use features such as rspec and cucumber to run all my tests (including user interaction). Moving from testing in Rails to iOS was a little harder to find well documented steps.

Testing in iOS

SenTestingKit (OCUnit) is the default testing library included in the Apple Docs (however some of the docs are pretty old). This is a simple unit testing kit for testing methods return the correct values, and runs very similar to the Java equivalent (JUnit). Most testing will be done with this kit when using Xcode, and with a few other libraries such as OCMock, you can develop iOS in a close-to-TDD way.

Creating tests using OCUnit is a very simple process that for each test, you create a method, and a simple comparison between objects, values etc.

A simple test can look like the following:

- (void)testFullName
{
    Person * person = [[Person alloc] init];
    [person setFirstName:@"John"];
    [person setLastName:@"Doe"];
    STAssertTrue([[person fullName] isEqualToString:@"John Doe"], @"");
    [person release];
}

Testing certain UI elements can also be done using OCUnit. This requires you to share your view controller and view with the test implementation file. Such that:

- (void) setUp {
    app_delegate    = [[UIApplication sharedApplication] delegate];
    myController    = app_delegate.myController;
    myView          = myController.view;
}

- (void)testUI
{
    [myController press:[myView viewWithTag:1]];
    STAssertTrue([[myController.lbl text] isEqualToString:@"John Doe"], @"");
}

Mocking

OCMock is a tool that is used for creating and testing mock objects in your application. Mock objects are simulated objects that mimic the behaviour of real objects in an application.

Advantages of using mock objects:

  • Testing objects that may not exist yet, or objects that may change behaviour
  • Testing whilst not effecting a database
  • Objects that may contain modified methods just for testing purposes

Mock objects can be tested as follows using OCMock:

myMock = [OCMockObject mockForClass:[NSString class]];
[[myMock expect] isEqualToString:@"myString"];
[myMock isEqualToString:@"myString"];

Instruments

Xcode’s instruments are great for testing your application when using real or test data. This was something that I only started using pretty recently. Basically, Instruments are a set of tools that enable you to profile and track processes when testing your applications.

Originally, I had many problems when learning about memory management in CocoaTouch, I soon learnt the basics, but my only real knowledge came when profiling my applications and actually seeing graphics of where memory was being retained well after it should have been released.

Instruments gives you memory counters and graphics to see where abouts and at what time memory is being retained when it shouldn’t. It also gives great insight into when memory is being allocated to objects, that may never get used. For example, initialising objects in viewDidLoad or in the controller init well before the object is being used may cause threads to hang whilst methods are being called, whereas a user may access the view controller without calling on the object. It also taught me when and where to initialise objects, and where to check if the object has already been initialised previously (for example, in methods that may get called a few times).

Real-World Testing

I’ve never been so impressed with any iOS libraries as much as I have been with TestFlight. TestFlight is an awesome site where you can upload IPA’s of your applications and have them delivered to testers over-the-air.

Using something like TestFlight means you can have them register their device using MobileSafari, and having their device id available for you to add them to the AdHoc provisioning on the Apple Developer site. TestFlight gives you an SDK that enables any error logs, NSLogs and checkpoints to be uploaded directly to the build reports on the website. Checkpoints on TestFlight let you check how deep testers are using your application. Other reports include how long a testers session lasts for and what devices the app has been installed onto.

I’d have to say, TestFlight is a god send when you have a group of beta testers that you want to deliver an app update to without having to email or sync any devices to iTunes.

Conclusion

Testing in iOS may not be widely spoken about, however there are definitely some great tools to get you started. I’ve added a list of links below to give you some insight in setting up your iOS testing environment:


NSObject for REST API Calls

In this tutorial I am going to teach you how to create your own NSObject that can make calls to your own web REST API and use it to retrieve the response received from the server. I am going to do this with the help of ASIFormDataRequest (ASIHTTPRequest). This will mean your iOS app will be able to make calls to your web server and download information to your app. The following tutorial is useful for if you want to perform tasks such as logging into an online account or registering a new user from within your application.

Getting Started - Initial Steps

Before we do anything, we need to create a new XCode iOS project. Do this, making sure that the project is a navigation-based project. Then we need to make sure that ASIHTTPRequest builds correctly in your project. So firstly, download it from their GitHub page here: Download Link. You then need to copy the files over into your project making sure iPhone projects have ASIAuthenticationDialog.h/m and Reachability.h/m included.Once you have included the files into your XCode project, the next step is to add the following frameworks into your project as well:

  • CFNetwork.framework
  • SystemConfiguration.framework
  • MobileCoreServices.framework
  • CoreGraphics.framework
  • libz.1.2.3.dylib

After these initial steps, you should be able to build your project without any errors. If you do come into any problems, refer to the ASIHTTPRequest setup instructions.

Let’s Do This!

Now you have the required classes in your project we can really start to have some fun! Let us start by creating an NSObject that will handle all the calls to your server and return only the necessary data back to your controllers. This is very useful as it keeps all the networking code away from your view controllers. By the end of this tutorial you will be able to use the following few lines of code to make a call to an API and return back information:

DWNetwork * network = [[DWNetwork alloc] init];
int response= [network sendTitle:@"My Title" withBody:@"This is the body for the post"];

For example, the two lines above would create our own object (in this case called DWNetwork) and the second line will use the object to send two strings to a web API. Later on in the tutorial I will show you how to use threading to make sure your application does not hang whilst this data is being sent.

Creating the NSObject

With your project already open, create a new file by clicking

File > New > New File > Objective-C Class > Subclass of NSObject > Next

and then save the file as DWNetwork.

When we initialise the object from our controller, we want to return our DWNetwork object and use this method to initialise any values within our object. So from DWNetwork.h, make the file contain the following:

#import
@interface DWNetwork : NSObject
- (DWNetwork *)init;
@end

And the counterpart (DWNetwork.m) to contain:

#import "DWNetwork.h"

@implementation DWNetwork

- (DWNetwork *)init
{
  return self;
}

@end

Now with what we have written we can already create an instance of our object ready to make calls to it. Now we need to include ASIHTTPRequest into the application and make our first API call!

Including ASIFormDataRequest in the NSObject

All we have to do for this is include #import "ASIFormDataRequest.h" at the top of the DWNetwork.m file.

Making our first API call

As an example, I am going to use my DWNetwork object to create a blog post on a website using POST data to its API. The URL string that I am going to use is:

http://website.com/api/blog/new_post

On the server this takes the following form POST keys:

@"post_title"
@"post_body"

So naturally, I want to create a public method in DWNetwork that allows me to pass in the title and body of a blog post that it will handle and send to the API in the correct format. To do this I first go into the header file and write the following line beneath the init:

- (int) sendTitle:(NSString *)title withBody:(NSString *)body;

This lets other controllers that are using my object to see that this method is available and therefore can be used. Then within the implementation, I will write the following code:

- (int) sendTitle:(NSString *)title withBody:(NSString *)body
{
  return nil;
}

Now our object has its first proper method that can be called (Albiet returns nil)!

Within this method we want to utilize the title and body that we pass into it plus use the URL that they are going to be posted to. Now we get down implementing ASIFormDataRequest that will take all this information and do something magical with it!

In the line above return nil; we will write the following:

NSString * urlString = [NSString stringWithFormat:@"http://website.com/api/blog/new_post"];

This is the URL that ASIFormDataRequest will use. Then we write:

ASIFormDataRequest * request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:urlString]];

If all is written down correctly, then building the project shouldn’t come up with any errors (although it will come up with a warning for a the unused request).Now we can start adding the title and body to our API call:

[request setPostValue:title forKey:@"post_title"];
[request setPostValue:body forKey:@"post_body"];

As I wrote previously, post_title and post_body are the keys that the REST API on the server will use for the title and body of the new blog post. So we use the [request setPostValue:(id) forKey:(NSString *)]; to pass in our variables.

Finally, we call:

[request startSynchronous];

to start up the connection to the API and transmit the data from our application.

Following this correctly so far, your DWNetwork.m file should look like the following:

#import "DWNetwork.h"
#import "ASIFormDataRequest.h"

@implementation DWNetwork

- (DWNetwork *)init
{
  return self;
}

- (int) sendTitle:(NSString *)title withBody:(NSString *)body
{
  NSString *urlString = [NSString stringWithFormat:@"http://website.com/api/blog/new_post"];

  ASIFormDataRequest *request = [ASIFormDataRequest requestWithURL:[NSURL URLWithString:urlString]];
  [request setPostValue:title forKey:@"post_title"];
  [request setPostValue:body forKey:@"post_body"];
  [request startSynchronous];

  return nil;
}

@end

At the moment (if the server is set up correctly) the request will send the data to the server. However, we are yet to know if this has failed or succeeded. So our next step is to return the response status code from the server.

Returning the response status code

This is a very simple step as ASIFormDataRequest has a built in method to call to retrieve the status code from the server.

All we have to do is replace return nil; with return [request responseStatusCode];

This will return an int that can be checked by your application to notify the user of the response from the API call. For example, the following numbers may be returned:

200 – Successful
400 – Bad Request
401 - Unauthorized
403 – Forbidden
404 – Not Found
405 – Method Not Allowed

ASIFormDataRequest also allows you to return an NSString with different information. We won’t be using it yet, but in the future you could return methods such as:

// Returns the status message
return [request responseStatusMessage];
// Returns the whole response as a string
return [request responseString];

Now we have an NSObject (DWNetwork) that:

  • Can be initialised
  • Get sent an NSString of the post title and post body
  • Send the strings to a server
  • Check the response status code

Now you just have to link this up to the UI!

Creating a new thread

Threads are really useful and will come in handy when you create a web heavy application such as a Twitter client, online game etc. And the great thing is, they are so simple to implement! All we need to do is create the method that the actual thread runs, and two other methods that will handle the response once the thread has completed.

Modify your RootViewController.m file so that the following is added:

- (void)post
{
  [NSThread detachNewThreadSelector:@selector(postThread:) toTarget:self withObject:nil];
}

- (void)postThread:(NSConnection *)connection
{
  NSLog(@"New thread started");
  NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

  DWNetwork * network = [[DWNetwork alloc] init];
  int response = [network sendTitle:titleField.text withBody:bodyField.text];
  if (response == 200) {
    [self performSelectorOnMainThread:@selector(postSuccess) withObject:nil waitUntilDone:YES];
  } else {
    [self performSelectorOnMainThread:@selector(postFailure) withObject:nil waitUntilDone:YES];
  }

  [pool release];
}

- (void)postSuccess
{
  UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"Success!"
                                                       message:@"Your post has been sent"
                                                      delegate:self
                                             cancelButtonTitle:@"Ok"
                                             otherButtonTitles:nil, nil];
  [alertView show];
  [alertView release];
}

- (void)postFailure
{
  UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"Error!"
                                                       message:@"Your post failed to send"
                                                      delegate:self
                                             cancelButtonTitle:@"Ok"
                                             otherButtonTitles:nil, nil];
  [alertView show];
  [alertView release];
}

Now when the user clicks a post button, the UI will not lock up, and also a UIAlertView will be shown with its contents dependant on whether the post succeeded or failed to send!


DWTagList for iOS

The other day I started development on a new iOS app for a website, but I soon came across the notion of having to list sets of tags in a nice way in a UITableViewCell.

I started off thinking the task would be quick hard compared to the rest of the application, however, with a few methods and a subclass of UIView it was done just short of 20 minutes.

I’ve set the code that you simply initialise it with the frame size, add an array of tags and then add it to your view. Almost everything is customisable including the padding, margins, background colour and font.

You can see the code here.

Post Archive

2014

December

2013

December

January

2012

October

September