Test-driven IOS Development

Graham Lee

Mentioned 6

As iOS apps become increasingly complex and business-critical, iOS developers must ensure consistently superior code quality. This means adopting best practices for creating and testing iOS apps. Test-Driven Development (TDD) is one of the most powerful of these best practices. Test-Driven iOS Development is the first book 100% focused on helping you successfully implement TDD and unit testing in an iOS environment. Long-time iOS/Mac developer Graham Lee helps you rapidly integrate TDD into your existing processes using Apple's Xcode 4 and the OCUnit unit testing framework. He guides you through constructing an entire Objective-C iOS app in a test-driven manner, from initial specification to functional product. Lee also introduces powerful patterns for applying TDD in iOS development, and previews powerful automated testing capabilities that will soon arrive on the iOS platform. Coverage includes Understanding the purpose, benefits, and costs of unit testing in iOS environments Mastering the principles of TDD, and applying them in areas from app design to refactoring Writing usable, readable, and repeatable iOS unit tests Using OCUnit to set up your Xcode project for TDD Using domain analysis to identify the classes and interactions your app needs, and designing it accordingly Considering third-party tools for iOS unit testing Building networking code in a test-driven manner Automating testing of view controller code that interacts with users Designing to interfaces, not implementations Testing concurrent code that typically runs in the background Applying TDD to existing apps Preparing for Behavior Driven Development (BDD) The only iOS-specific guide to TDD and unit testing, Test-Driven iOS Development covers both essential concepts and practical implementation.

More on Amazon.com

Mentioned in questions and answers.

I'm trying out test driven development in a toy project. I can get the tests working for the public interface to my classes (although I'm still on the fence because I'm writing more testing code than there is in the methods being tested).

I tend to use a lot of private methods becuase I like to keep the public interfaces clean; however, I'd still like to use tests on these methods.

Since Cocoa is a dynamic language, I can still call these private methods, but i get warnings in my tests that my class may not respond to these methods (although it clearly does). Since I like to compile with no warnings here are my questions:

  1. How do i turn off these warnings in Xcode?
  2. Is there something else I could do to turn off these warnings?
  3. Am I doing something wrong in trying 'white box' testing?

I was dealing with the same issue when I started with TDD few days ago. I've found this very interesting point of view in Test-Driven iOS Development book:

I have often been asked, “Should I test my private methods?” or the related question “How should I test my private methods?” People asking the second question have assumed that the answer to the first is “Yes” and are now looking for a way to expose their classes’ private interfaces in their test suites.

My answer relies on observation of a subtle fact: You already have tested your private methods. By following the red–green–refactor approach common in test-driven development, you designed your objects’ public APIs to do the work those objects need to do. With that work specified by the tests—and the continued execution of the tests assuring you that you haven’t broken anything—you are free to organize the internal plumbing of your classes as you see fit.

Your private methods are already tested because all you’re doing is refactoring behavior that you already have tests for. You should never end up in a situation where a private method is untested or incompletely tested, because you create them only when you see an opportunity to clean up the implementation of public methods. This ensures that the private methods exist only to support the class’s that they must be invoked during testing because they are definitely being called from public methods.

I'm starting to program in iOS and I've been wondering if I should do Test Driven Development.

I come from a rails background, where TDD is a way of life for many and where the TDD-tools are great.

How are the views on TDD in iOS?

Do you have any resources for learning it?

Hi I looking for really good tutorial for iOS TDD, would you please help me!

what is the best iOS TDD book, blog --> tutorial (I had different research on google but since I don't have enough knowledge about " iOS TDD " I don't know which one is the best)

Thanks in advance!

There are many stuff about TDD on iOS available on the web..

If you choose using GHUnit, I recommend you using XCode's template available here: https://github.com/zenkimoto/ghunit-ocmock-xcode4-template

and if you use ARC, I recommend changing your GHUnitTestMain.m with this:

http://hataewon.tumblr.com/post/17502311021/continuous-integration-for-cocoa

At last, I recommend the book Test Driven iOS Development from Graham Lee http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183

(Sorry, as a new user I could not post more than two links)

I want to implement a webservice client in iOS which uses SOAP and XML for requests/responses. My view starts the initial businnes logic (a user presses a button or something and initiates some businnes method called method_A).

So I have a class with method_A and this method checks if the user is logged in etc and then starts the request asynchronous via the SOAPConnector-class. So the UI is not blocked (asynchronous). The SOAPConnector-class takes the XML and handles the requests. I use therefore NSURLRequest and NSURLConnection with sendSynchronousRequest.

The response is sended back to a Response-class which takes the response. This class then wants to parse the response XML. Therefore I use an extra class called XMLManager which uses NSXMLParser to parse the xml. But again here we need a delegate which gets the parsed xml. And again after parsing I have to implement an extra method to give back the parsed xml to the first class which initiated the request.

I am really wondering if this is the right way. The first problem is asnychronous request to not block the UI (the first callback). The second problem is the parsing where I am forced to use the delegate (the second callback). This results in a lot of classes and methods and I doubt this is the right way. The classes' only purpose is to manage the delegate and async problems. So I am asking for any suggestions and help how to solve this. Do you know some good design patterns to solve this problem?

Apart from some inconsistencies in the way you describe the design patterns you've selected:

and then starts the request asynchronous

vs.

I use therefore NSURLRequest and NSURLConnection with sendSynchronousRequest.


That said, your approach seems sound. Addressing the issues you've identified:

I use therefore NSURLRequest and NSURLConnection with sendSynchronousRequest.

Isn't that the purpose of using an asynchronous API? If your NSURLConnection is really operating asynchronously, that issue should be covered.

The second problem is the parsing where I am forced to use the delegate

This approach does result in more classes, delegation, etc. but it conforms to best practices when it comes to testing. If you are performing unit testing or other testing strategies (you are aren't you?) then testing in isolation is all the harder unless you breakdown this process functionally.

If you have access to the book Test-Drive iOS Development there is a great section on the best practices for consuming web services with a view toward testability.

I am new to unit testing and am trying things out.

I created a view controller with 1 button (get sum), and 3 textfields (input 2 numbers and output the sum).

int aNum = [self.firstNumber.text intValue];
int bNum = [self.secondNumber.text intValue];

sum = aNum + bNum;
self.total.text = [NSString stringWithFormat:@"%i", sum];
[self dismissKeyboard];

And my testing codes:

vc = [[TestingViewController alloc] init];
vc.firstNumber.text = @"1";
vc.secondNumber.text = @"2";

[vc getSum:nil];

STAssertTrue([vc.total.text isEqualToString:@"3"], @"total should be 3");

The test failed because I have tried to work with UI elements.

My questions is: is it possible to test with UI elements like this? How would I write a test to achieve this?

Thanks guys!

Yes, testing UI element in unit tests is definitely possible.

Techniques for this and more (including testing code which relies on networks) is covered in Test-Driven iOS Development (Developer's Library) by Graham Lee is a great resource: http://www.amazon.com/Test-Driven-iOS-Development-Developers-Library/dp/0321774183

Could be that the view isn't getting loaded. I had the same problem when I started with unit testing on iOS. Try calling [vc loadView]; after you init the viewcontroller.

vc = [[TestingViewController alloc] init];
[vc loadView];

I'm following the examples in Test driven iOS Development and in one case there is a unit test that ensures that a delegate gets a 'dumbed down' version of an error method. So without going into too many details here are the relevant objects:

  • communicator: object responsible for making the network calls
  • manager: instructs the communicator to make calls and then pushes result to its delegate.
  • delegate: manager delegate that conforms to the StackOverflowManagerDelegate protocol. gets the results and processes it.

so this is what the test is:

@implementation QuestionCreationTests

{
    @private
    StackOverflowManager *mgr;
}
- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
    MockStackOverflowManagerDelegate *delegate =
    [[MockStackOverflowManagerDelegate alloc] init];
    mgr.delegate = delegate;
    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];
    [mgr searchingForQuestionsFailedWithError: underlyingError];
    XCTAssertFalse(underlyingError == [delegate fetchError],
                  @"Error should be at the correct level of abstraction");
}

this is the implementation of searchingForQuestionsFailedWithError in StackOverflowManager, where the manager simply dumbs down the original error returned by the communicator and sends the dumbed down version to the delegate.

- (void)searchingForQuestionsFailedWithError:(NSError *)error {
    NSDictionary *errorInfo = [NSDictionary dictionaryWithObject: error
                                                          forKey: NSUnderlyingErrorKey];
    NSError *reportableError = [NSError
                                errorWithDomain: StackOverflowManagerSearchFailedError
                                code: StackOverflowManagerErrorQuestionSearchCode
                                userInfo:errorInfo];
    [delegate fetchingQuestionsOnTopic: nil
                       failedWithError: reportableError];
}

the author suggests that for this to work.. we actually have to create a mock object for the manager delegate like so:

@interface MockStackOverflowManagerDelegate : NSObject <StackOverflowManagerDelegate>
@property (strong) NSError *fetchError;

@end

@implementation MockStackOverflowManagerDelegate

@synthesize fetchError;

- (void)fetchingQuestionsOnTopic: (Topic *)topic
                 failedWithError: (NSError *)error {
    self.fetchError = error;
}

@end

this is the declaration of StackOverflowManagerDelegate:

@protocol StackOverflowManagerDelegate <NSObject>    
- (void)fetchingQuestionsOnTopic: (Topic *)topic
                 failedWithError: (NSError *)error {    
@end

Question: I've been going over all the examples of the book and trying to use OCMock instead of the manually made ones like the author is doing.. (i just thought it would be a lot less time consuming). Everything has worked so far.. but i'm stuck here.. how do I fake a property called fetchError on delegate? This is what I have right now:

- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
    id <StackOverflowManagerDelegate> delegate = 
       [OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
    mgr.delegate = delegate;

    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];

    [mgr searchingForQuestionsFailedWithError: underlyingError];

    // compiler error here: no known instance method for selector 'fetchError'
    XCTAssertFalse(underlyingError == [mgr.delegate fetchError], @"error ");
}

In the guts of manager, manager calls fetchingQuestionsOnTopic on the delegate.. I know I can fake that method by using [[[delegate stub] andCall:@selector(differentMethod:) onObject:differentObject] fetchingQuestionsOnTopic:[OCMArg any]] where differentMethod would do whatever I want it to do.. I just don't know what to do with the result of differentMethod: i don't know how to store it in a mocked out property of delegate.


update: as a follow up to the answer below.. here is the implementation of unit test that ensures that the underlying error is still made available to the delegate:

- (void)testErrorReturnedToDelegateDocumentsUnderlyingError {
    MockStackOverflowManagerDelegate *delegate =
    [[MockStackOverflowManagerDelegate alloc] init];
    mgr.delegate = delegate;
    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];
    [mgr searchingForQuestionsFailedWithError: underlyingError];
    XCTAssertEqual([[[delegate fetchError] userInfo]
                          objectForKey: NSUnderlyingErrorKey], underlyingError,
                         @"The underlying error should be available to client code");
}

and here is the OCMock version of it:

- (void)testErrorReturnedToDelegateDocumentsUnderlyingErrorOCMock {
    id delegate =
    [OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
    mgr.delegate = delegate;

    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];

    [[delegate expect] fetchingQuestionsFailedWithError:
       [OCMArg checkWithBlock:^BOOL(id param) {

        return ([[param userInfo] objectForKey:NSUnderlyingErrorKey] == underlyingError);

    }]];

    [mgr searchingForQuestionsFailedWithError: underlyingError];

    [delegate verify];

}

Try this:

- (void)testErrorReturnedToDelegateIsNotErrorNotifiedByCommunicator {
    id delegate = 
       [OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
    mgr.delegate = delegate;

    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];

    [[delegate expect] fetchingQuestionsOnTopic:OCMOCK_ANY 
                                failedWithError:[OCMArg isNotEqual:underlyingError]];

    [mgr searchingForQuestionsFailedWithError:underlyingError];

    [delegate verify];
}

To test the domain, code and userInfo of the error reported by the manager (if I remember well it's another test case in that book - I read it long time ago) you could do something like this:

    id <StackOverflowManagerDelegate> delegate = 
       [OCMockObject mockForProtocol:@protocol(StackOverflowManagerDelegate)];
    mgr.delegate = delegate;

    NSError *underlyingError = [NSError errorWithDomain: @"Test domain"
                                                   code: 0 userInfo: nil];

    NSError *expectedError = [NSError errorWithDomain:@"expected domain" 
                                                 code:0/* expected code here*/ 
                                             userInfo:@{NSUnderlyingErrorKey: underlyingError}];
    [[delegate expect] fetchingQuestionsOnTopic:OCMOCK_ANY 
                                failedWithError:expectedError];

    [mgr searchingForQuestionsFailedWithError: underlyingError];

    [delegate verify];