Double benefit of unit testing

As soon as a programming project becomes a little more sophisticated, you will be confronted with the development of complex algorithms which requires intense testing and debugging. As this happened to me the last time a few weeks ago, I decided to write a short article about why I think, that unit testing is a perfect instrument for developing and debugging complex algorithms in a controlled environment and so it is more than just supporting regression tests during refactorings and enhancements of an app.

The app I’m currently developing allows to create multiple instances of a certain object, which is quite usual for a lot of apps 🙂
There are some main requirements to the object names:

  1. Default object names should recall somehow the type of the object the user has just created.
  2. Nevertheless object names should be unique.
  3. The user shouldn’t be constrained to interrupt his work entering a unique object name as new objects are created by touch in a graphical environment.

The object class therefore contains a property which proposes a readable name for the specific object which was just created by the user:

@property (nonatomic, readonly, strong) NSString *proposedObjectName
- (NSString*) proposedObjectName
{
 return @"ObjectName";
}

Clearly the proposed object name is not unique over every created object, so we need an additional method to uniqueing object names.

I wanted to apply a well known scheme for automatic naming of new objects which consists into appending an incremental numeric suffix to the given object name.

As the objects are stored into a core data managed object storage, we need to define a query, which retrieves objects from the store, whose names corresponds to the required naming scheme. The operation “beginswith” is just perfect for this purpose. We give the predicate our proposed name, the query checks for all entries which names starts with this proposed name.

- (NSString*)findUniqueName:(NSString*)name inObjectCollection:(MyCollection *)collection
{
    NSFetchedResultsController *nameFetch = [MyObject list:nil sortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]] predicate:[NSPredicate predicateWithFormat:@"(collection = %@) AND (name beginswith %@)",collection, [self stripNumericEnding:name]]];
    NSError *error;
    [nameFetch performFetch:&error];
    if (error == nil) {
        // Name is already unique so just return it
        if ([[nameFetch fetchedObjects] count] == 0) {
            return name;
        }
        NSError *error;
        NSRegularExpression *regEx =
        [NSRegularExpression regularExpressionWithPattern:@"([a-z0-9]*)([0-9]+)"
                                                  options:NSRegularExpressionCaseInsensitive
                                                    error:&error];
        if (error == nil) {
            NSArray *objects = [nameFetch fetchedObjects];
            NSString *nameFound = ((MyObject*)[objects lastObject]).name;
            NSArray *matches = [regEx matchesInString:nameFound
                                              options:0
                                                range:NSMakeRange(0, [nameFound length])];
            // Last name found does not contain any number, so just append 1 to it and return
            if ([matches count] == 0) {
                return  [NSString stringWithFormat:@"%@1", nameFound];
            }
            else {
                // should be just one match
                NSTextCheckingResult *match = matches[0];
                /// This shouldn't happen at all - but who knows, better return somethin unique
                if ([match numberOfRanges] < 3) {
                    return [NSString stringWithFormat:@"%@1",nameFound];
                }
                // Get the number of the last object (even if there are gaps in between)
                NSRange range = [match rangeAtIndex:2];
                NSInteger counter = [[nameFound substringWithRange:range] integerValue];
                counter++;
                return [nameFound stringByReplacingCharactersInRange:range withString:[NSString stringWithFormat:@"%ld",(long)counter]];
            }
        }
        else {
            NSLog(@"An error was thrown in [%@ %s]:%@",NSStringFromClass([self class]),__PRETTY_FUNCTION__,error);
        }

    }
    else {
        NSLog(@"An error was thrown in [%@ %s]:%@",NSStringFromClass([self class]),__PRETTY_FUNCTION__,error);
    }
    // Make sure a name is returned in any case
    return name;
}

The given method “findUniqueName” starts by obtaining a sorted list of all objects whose names corresponds to the naming convention “ObjectName” + [Numeric Suffix], where numeric suffix may be empty.
If no object was found at all, the name is already unique so the method just returns the name given in the parameter list.
For the performance and simplicity reasons, we just consider the last object in the list, which means that gaps in the enumeration of object names will not be filled by the lookup.
A regular expression is used to extract the numeric suffix from the name found in the database. If no numeric suffix is found, a “1” is appended to the given name and returned as the proposed unique name for the object. If a suffix is found, its numerical value is incremented. The suffix is replaced in the last name found in the list so we can be sure, that the new name is unique.

Of course we don’t consider concurrent database access which could produce duplicates during the execution of this method. Therefore other mechanisms should be applied to make sure, that duplicates are handled even in concurrent access situations.

There are a lot of cases where such a function may fail due to programming errors, since many different technologies and framework functionalities are applied here. We have database queries, regular expressions, string substitutions and last but not least a reasonably complex workflow that may be subject to a failure. In addition to the many possible error sources it would be interesting to test all the different execution paths in a controlled environment in order to find out if a programming error occurred.

Besides all the possible testing scenarios I personally prefer an environment, where I can do isolated tests and debugging without all the user interface stuff and without having to navigate to the function I want to test every time I recompile a new version. Here’s the point where unit testing comes to its double benefit: fast and easy testing during development and debugging and an automated regression test to be reused whenever something has changed. Some of you will know this as an element of “Test driven development”.

Now since we are using Xcode’s unit testing framework nearly everything is in place to support the development of unit test. The class we want to test is based on core data, so we have to prepare something to be able to interact with a managed object context. Here’s how we do it:

- (void)setUp
{
    [super setUp];
    /// Setup managed object context
    NSArray *bundles = [NSArray arrayWithObject:[NSBundle bundleForClass:[self class]]];
    _managedObjectModel = [NSManagedObjectModel mergedModelFromBundles:bundles];
    _persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:_managedObjectModel];

    _persistentStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSInMemoryStoreType configuration:nil URL:nil options:nil error:NULL];
    XCTAssertNotNil(_persistentStore, @"Unable to create in-memory store");

    _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
    [_managedObjectContext setPersistentStoreCoordinator:_persistentStoreCoordinator];    /// Create a project

    _collection = [MyCollection insert];
    _collection.name = @"Test collection name";

    [self saveChanges];

}

Xcode’s testing environment offers a standard setup method for preparing the individual tests of a test case. First we call the method of the super class, you never know… from the testing bundle (which also should refer to the managed object model of your app) the managed object model is extracted and assigned to the persistent store coordinator via its initializer. The persistent store required for the test execution is allocated and initialized along with a managed object context to perform all required database operations. Our objects are part of an object collection, which we also initialize during our setup phase. All the changes (here the insertion of the new collection) are saved to the managed object context via the “saveChanges” method. See the “saveChanges” in the following for completeness:

- (void)saveChanges
{
    if ([_managedObjectContext hasChanges]) {
        NSError *error = nil;
        if (![_managedObjectContext save:&error]) {
            NSLog(@"Failed to save to data store: %@", [error localizedDescription]);
            NSArray* detailedErrors = [[error userInfo] objectForKey:NSDetailedErrorsKey];
            if(detailedErrors != nil && [detailedErrors count] > 0) {
                for(NSError* detailedError in detailedErrors) {
                    NSLog(@"  DetailedError: %@", [detailedError userInfo]);
                }
            }
            else {
                NSLog(@"  %@", [error userInfo]);
            }
        }
        [_managedObjectContext processPendingChanges];
    }
}

Now we can concentrate on the test method. In Xcode test methods are recognized automatically by naming them with the prefix “test” and having a void return type and no parameters. We provide a unit test, which checks the names of newly inserted objects for conformance to the previously discussed schema:

- (void)testPanelSerialization {
    /// Create new object
    MyObject *object = [MyObject insert];
    object.name = @"TestObjectName";
    panel.collection = _collection;
    /// Save it
    [self saveChanges];

    /// now copy it
    object = [object copy];
    object.name = [object findUniqueName:object.name inObjectCollection:_collection];

    /// Assert that name is correct
    XCTAssertTrue([object.name isEqualToString:@"TestObjectName1"], @"Object name was not created properly");

    /// Save and build the next
    [self saveChanges];

    object = [object copy];
    object.name = [object findUniqueName:@"TestObjectName" inObjectCollection:_collection];

    /// Test method when name with numeric suffix is found in the database
    XCTAssertTrue([panel.name isEqualToString:@"TestObjectName2"], @"Object name (2) was not created properly");

    [self saveChanges];

    /// Check name proposals which already contain a numeric suffix
    object = [object copy];
    object.name = [object findUniqueName:@"TestObjectName2" inObjectCollection:_collection];

    XCTAssertTrue([panel.name isEqualToString:@"TestPanelName3"], @"Object name (3) was not created properly.");

}

The test method tries to walk through all paths of the “findUniqueName” method checking if the created names are as expected. I used this method for debugging as well, without the need of repeatedly starting the app and navigating all the way to the function to test, which speeded up development significantly.

Xcode also offers a tear down method to clean up all the stuff that has been created and initialized during testing. Here it is used to clean up all created entities, just by deleting all the collections of objects from the database, this also covers other potential tests:

- (void)tearDown
{
    /// Remove the project
    NSFetchedResultsController *fetchCollections = [MyCollection list:nil sortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]]];
    NSError *error;
    [fetchProjects performFetch:&error];
    if (error == nil) {
        NSArray *collections = [fetchCollections fetchedObjects];
        [collections enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
            [(MyCollection*)obj delete];
        }];
    }
    else {
        NSLog(@"An error was thrown in [%@ %s]:%@",NSStringFromClass([self class]),__PRETTY_FUNCTION__,error);
    }

    [self saveChanges];

    [super tearDown];
}

I personally use to write unit tests whenever a similar scenario appears or just before refactoring of an existing function, this is my way of doing test driven development in a very pragmatic way and just when it is really needed. It definitely helps me developing and refactoring robust code faster than just doing simple manual or automated application tests.