This report shows an example of unit testing in Objective-C using iPhone Unit Testing
framework for a map view based application. Unit tests target your code at the
lowest possible level - the input/output of a single method (a method is often
the smallest testable
External data sources are less reliable than, for example, a static read-only file on the file system. When applications rely on external data sources such as the Internet and device hardware, availability and validity of data are undoubtedly major risks. This is due to the fact that the likelihood of incidents such as information has been moved, information not being in expected format or network disconnection is high. Therefore, checking if an application handles data related matters properly can be the centre of focus in tests.
The goal here is to make sure a proper error is raised when coordinates, either latitude or longitude, are out of range. The code snippet below shows data validation in the map view controller. Note that all data handling and validation methods are supposed to be in a separate class in model layer. However, as the goal here is not to demonstrate MVC pattern and to make it quicker, data related matters are being handled in view controllers.
Code snippet 1: data validation
Fortunately, XCODE makes it fairly easy to add unit tests to applications. What needs to be done in the beginning is to make sure
Then all needs to be done is clicking and holding Run button in the upper left of XCODE IDE, and choose test from the popup menu.
If you forgot to include unit tests in an application, or you want to add unit tests to an existing application, you simply add a new target to the project, and add a “Cocoa Touch Unit Testing Bundle” to your app.
Next is to write appropriate test cases to see if data validation code works properly and effectively. There are 4 test cases in the example map view application.
The results of tests are shown in XCODE output window (as shown in figure 6 bolew). Tests marked with
unitof code). If every single method behaves as you expect, so will your program. What percentage of your code is
coveredby unit tests is called unit test coverage. 100% coverage is not only rare, but unrealistic. You should determine which classes and methods are to get the most
attentionfrom your tests. This can be achieved by answering a few questions like
how often the implementation changesor
if it takes input from an unpredictable source, like a user or a network connection.
External data sources are less reliable than, for example, a static read-only file on the file system. When applications rely on external data sources such as the Internet and device hardware, availability and validity of data are undoubtedly major risks. This is due to the fact that the likelihood of incidents such as information has been moved, information not being in expected format or network disconnection is high. Therefore, checking if an application handles data related matters properly can be the centre of focus in tests.
Example map view application
The example map view application in question here consists of two view controllers. First, there is a table view controller which loads some coordinates from a CSV file. By selecting any of the coordinates from the table view, the second view, the map view, appears showing the chosen point. Screen shots of the application are presented below (source code of the application is provided at the end of the report).
The goal here is to make sure a proper error is raised when coordinates, either latitude or longitude, are out of range. The code snippet below shows data validation in the map view controller. Note that all data handling and validation methods are supposed to be in a separate class in model layer. However, as the goal here is not to demonstrate MVC pattern and to make it quicker, data related matters are being handled in view controllers.
- (void)setMapDetailsWithLatitude:(double)
latitude andWithLongitude:(double)longitude
{
@try {
if ( latitude > 90 || latitude < -90 ) {
NSException* e = [NSException exceptionWithName:@"Bad Location"
reason:@"Invalid latitude"
userInfo:nil];
@throw e;
}
if ( longitude > 180 || longitude < -180 ) {
NSException* e = [NSException exceptionWithName:@"Bad Location"
reason:@"Invalid longtitude"
userInfo:nil];
@throw e;
}
MKCoordinateSpan coordinationSpan;
coordinationSpan.latitudeDelta = 0.1;
coordinationSpan.longitudeDelta = 0.1;
CLLocationCoordinate2D locationCoordinate;
locationCoordinate.latitude =
latitude;
locationCoordinate.longitude =
longitude;
MKCoordinateRegion region;
region.span=coordinationSpan;
region.center=locationCoordinate;
mapView.mapType = MKMapTypeStandard;
[mapView setShowsUserLocation:YES];
[mapView setRegion:region
animated:TRUE];
}
@catch (NSException *exception) {
NSException* e = [NSException exceptionWithName:@"Bad Location"
reason:@"Invalid Location Parameters"
userInfo:[exception userInfo]];
@throw e;
}
}
Fortunately, XCODE makes it fairly easy to add unit tests to applications. What needs to be done in the beginning is to make sure
Include Unit Testscheckbox is checked when creating a new application.
Then all needs to be done is clicking and holding Run button in the upper left of XCODE IDE, and choose test from the popup menu.
If you forgot to include unit tests in an application, or you want to add unit tests to an existing application, you simply add a new target to the project, and add a “Cocoa Touch Unit Testing Bundle” to your app.
Next is to write appropriate test cases to see if data validation code works properly and effectively. There are 4 test cases in the example map view application.
- testInvalidLatitude – Tests if an invalid latitude would cause an exception or not (latitude should be between -90 and +90).
- testInvalidLongitude - Tests if an invalid longitude would cause an exception or not (longitude should be between -180 and +180).
- testValidCoords – Tests if valid latitude and longitude would not cause an exception.
- testAllCoordsFromFile – Tests all the values provided in the gps_coords.csv file.
- (void)testInvalidLatitude
Code snippet 2: test cases in the test class
{
MapViewController* mapVC = [[MapViewController alloc] initWithNibName:@"MapViewController" bundle:nil];
STAssertThrowsSpecificNamed([mapVC setMapDetailsWithLatitude:91 andWithLongitude:0],NSException, @"Bad Location", @"Invalid latitude or longitude values should
raise an exception");
[mapVC release];
}
- (void)testInvalidLongitude
{
MapViewController* mapVC = [[MapViewController alloc] initWithNibName:@"MapViewController" bundle:nil];
STAssertThrowsSpecificNamed([mapVC setMapDetailsWithLatitude:0 andWithLongitude:181],NSException, @"Bad Location", @"Invalid latitude or longitude values should
raise an exception");
[mapVC release];
}
- (void)testValidCoords
{
MapViewController* mapVC = [[MapViewController alloc] initWithNibName:@"MapViewController" bundle:nil];
STAssertNoThrow([mapVC setMapDetailsWithLatitude:27 andWithLongitude:153],@"Valid
latitude or longitude values should NOT raise an exception");
[mapVC release];
}
- (void)testAllCoordsFromFile
{
NSString* filePath = [[NSBundle mainBundle] pathForResource:@"gps_coords"
ofType:@"csv"];
ofType:@"csv"];
NSString* gpsCoords = [NSString stringWithContentsOfFile:filePath
usedEncoding:nil
error:nil];
usedEncoding:nil
error:nil];
NSMutableArray* mutableArray = [[NSMutableArray alloc] initWithArray:[gpsCoords componentsSeparatedByString:@"\n"]];
MapViewController* mapVC = [[MapViewController alloc] initWithNibName:@"MapViewController" bundle:nil];
double lat;
double lng;
for (NSInteger i=0; i<[mutableArray count]; i++) {
@try {
lat
= [[[[mutableArray objectAtIndex:i] componentsSeparatedByString:@";"] objectAtIndex:0] doubleValue];
lng
= [[[[mutableArray objectAtIndex:i] componentsSeparatedByString:@";"] objectAtIndex:1] doubleValue];
STAssertNoThrow([mapVC setMapDetailsWithLatitude:lat andWithLongitude:lng],@"Valid latitude or longitude
values should NOT raise an exception");
}
@catch (NSException* e){
NSLog([NSString stringWithFormat:@"line %d is not in expected format",i]);
}
}
[mapVC release];
[mutableArray release];
}
The results of tests are shown in XCODE output window (as shown in figure 6 bolew). Tests marked with
passedkeyword are successful tests. It means that, for instance,
expected exception was thrown, or
no exception was thrown when it was not meant to, or
the return value of some method are as expectedand so on.
Conclusion
Variety of tests can be implemented such as STAssertNotNil, STAssertTrue, STAssertThrows, STAssertThrowsSpecific, STAssertThrowsSpecificNamed and STAssertEquals. In addition, tests are debug-able using XCODE which is very helpful. Taking advantage of unit testing, to some extent, can guarantee the functionality of any application. Having a variety of test cases that are designed and implemented accurately can help to minimize the risk of failure. However, unit testing is absolutely not a substitute for pressure tests or tests including real users whatsoever. Unit testing is specifically useful to test the desired functionality of the application in known dangerous circumstances, where you know your code might break.