Monday, June 18, 2012

Presentation for JUG In Sao Jose dos Campos

Hello! Long time no see!

I'll make a presentation about Test Driven Development, with focus on Android development on the JUG Group in Sao Jose dos Campos this weekend.

These are the slides I will be presenting. It's in portuguese, but I'll upload a translation to English as soon as possible.

Here is the link to the presentation: http://www.slideshare.net/rafaeladson/android-testing-ptbr

Sunday, March 25, 2012

Intern-droid now on github.


Some time ago, I made intern-ios, my framework for iPhone and iPad applications, open on github.com. I also wanted to make intern-droid open, but as at the time I was developing for android (since I was focusing my efforts on kotoba v0.1 for iOS) and the intern-droid would not be platform independent, I postponed publishing it on github.

Today I began work in kotoba v0.2 for android (now the idea is to try to keep development of the two versions in parallel), and as promised, I did some modifications on intern-droid in order to release it on github.

So, if you want to check it out, please do so at https://github.com/rafaeladson/intern-droid

Wednesday, March 21, 2012

Starting iOS application in desired orientation

I have an storyboard based application, and I noticed that when I started the application on iPhone, it always started on landscape, then after the application started it would then autorotate to portrait if the cell phone was in portrait mode. When I ran the application on the iPad instead, it would always start on portrait. So I searched the web to find how to fix it, and by examining some posts about it and tinkering a little with Xcode, I found the following solution to the problem: Inside the application target, there's a -Info.plist file. This file contains some configuration properties of the application. Among those, there's a "Supported interface orientations" property for both iPhone and iPad. My configuration for the iphone was the following: 

Item 0 Landscape (left home button)
Item 1 Landscape (right home button)
Item 2 Portrait (button home button)
I found out that the order of these items matter. The application will always start on the first orientation in this list. I was then able to solve my problem by dragging the Portrait (button home button) to the top of the list so that it turned into Item 0. Then, after I started my application again, it started in Portrait by default. Just posting in the case that someone has the same problem.

Sunday, March 18, 2012

TDD on iOS - Part I.


I want to make a blog post about how to practice TDD on iPhone. My approach in this tutorial will be:

  • Build the prototype (the screen of the application)
  • For every functionality, first we will create the tests, then we will implement the code that make the tests pass.

What I won’t do in this post:
  • I won’t make a full application. I’ll try to illustrate how someone could use TDD to make a full application as an example, but I won’t try to do one.
  • I won’t worry about error conditions, like cases where a field in a form is obligatory, but the user decides not to fill it. However, from the examples that I do give, the approach to test these kind of conditions will hopefully become trivial.


The Prototype
I want to make for this example a birthday reminder application. It’s a very simple application that have the following user cases:
  • A user should be able to save the birthday date from his friends.
  • The user should be able to see all the birthday dates that he saved.

Based on these user cases, I created this prototype:



So, the user will see the list of people in a tableView. Every person will have on the cell his name and his birthday listed. The user will also be able to register new friends by clicking on the + button, typing the details for the friend, and clicking on the Save button.
The new registered friend should then appear on the table view.

I won’t save the data on the database for this example. It’s possible to do TDD in database oriented applications, and if you  want to know how I suggest you take a look at https://github.com/rafaeladson/intern-objc/blob/master/InternIOSTest/DataManagerBaseTest.m. Instead, I’ll use an array shared in the application delegate to simplify the example.

I’ll have two controllers: PeopleTableViewController, which will list the people and their respective birthdays, and NewPersonViewController, which will manage the view where the user can save new People.

The first test
First I’ll make the setup of the first test. In this setup, I’ll try to get the view from the storyboard. I’ll use the same approach I did on  http://yetanotherdevelopersblog.blogspot.com.br/2012/03/how-to-test-storyboard-ios-view.html

Here’s the setup of the first test:

#import "NewPersonViewControllerTest.h"
#import "NewPersonViewController.h"

@interface NewPersonViewControllerTest()

@property (strong, nonatomic) NewPersonViewController *controller;

@end

@implementation NewPersonViewControllerTest

@synthesize controller = _controller;


-(void) setUp {
   
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
    self.controller = [storyboard instantiateViewControllerWithIdentifier:@"NewPersonViewController"];
    [self.controller performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES];

}

-(void) testPreConditions {
    STAssertNotNil(self.controller, nil);
}


@end

When I try to run this test, it will fail with the following message:

error: testPreConditions (NewPersonViewControllerTest) failed: Storyboard () doesn't contain a view controller with identifier 'NewPersonViewController'

To fix it, I have to edit my storyboard file to define that this controller has the identifier called new person view controller, in the same way I explained in my storyboard testing tutorial. After I do this, the test passes.

Now, for the first test, I’ll fill in the two fields and click on save. If all goes well, the array should end up with one element.


-(void) testSaveANewPerson {
    [self.controller.nameTextField performSelectorOnMainThread:@selector(setText:) withObject:@"Mike" waitUntilDone:YES];
    [self.controller.birthdayTextField performSelectorOnMainThread:@selector(setText:) withObject:@"October 10th" waitUntilDone:YES];
   
    [self.controller onSaveAction:nil];
   
    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    NSArray *people = delegate.people;
    int peopleCount = [people count];
    STAssertEquals(1, peopleCount, nil);
   
    Person *personInArray = [people objectAtIndex:0];
    STAssertEqualObjects(@"Mike", personInArray.name, nil);
    STAssertEqualObjects(@"October 10th", personInArray.birthday, nil);
   
}

Also, when I was making this test, I created the infrastructure for the test to compile, defining the outlets for nameTextField and birthdayTextField, the IBAction for onSaveAction and the people array on the application  delegate. However, I haven’t done any implementation yet. When I try to run this test, I get the following errors:

file://localhost/Users/rafael/dev/learning/examples/TDD/TDDTests/NewPersonViewControllerTest.m: error: testSaveANewPerson (NewPersonViewControllerTest) failed: '1' should be equal to '0':

file://localhost/Users/rafael/dev/learning/examples/TDD/TDDTests/NewPersonViewControllerTest.m: error: testSaveANewPerson (NewPersonViewControllerTest) failed: '<18a57906>' should be equal to '<00000000>':

file://localhost/Users/rafael/dev/learning/examples/TDD/TDDTests/NewPersonViewControllerTest.m: error: testSaveANewPerson (NewPersonViewControllerTest) failed: '<28a57906>' should be equal to '<00000000>':


Ok, so now I now that my tests are failing because the array should have 0 elements and it’s not initialized, so I have trash in the variables. Let’s try to implement by first modifying the application delegate to initialize the people’s array:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    self.people = [[NSMutableArray alloc] init ];
    return YES;
}

Now, let’s change the onSaveAction to save the person:

- (IBAction)onSaveAction:(id)sender {
    Person *person = [[Person alloc] init];
    person.name = [self.nameTextField text];
    person.birthday = [self.birthdayTextField text];
   
    AppDelegate *delegate = [[UIApplication sharedApplication] delegate];
    [delegate.people addObject:person];
    [self.navigationController popViewControllerAnimated:YES];
}



Now, the tests pass.

In the last line, I did a popViewController to assure that after saving a new person I would automatically go back to the previous screen. If I wanted to test this behaviour, I would propably do it in an acceptance test rather than in a integration test like this one.



Coming up next

This is getting long, so I’ll break in several posts. In the next post I’ll tell you how you can implement the tableview part of the test and how to test segue behaviour.








You can check the code for this post at github. I'll change this code when I implement the following blog posts, so it's possible that the code will be a little different than what you saw here, but the principle will be the same.


Till then!
About Intern

Intern is now open source.

I’ve been busy, so I neglected to post this in quite some time, but I have made Intern open source.

Intern was created because I did not want to repeat some custom implementations in every application that I do for iOS. Probably every table view that I program will have the default implementation to delete an item, I’ll probably aways create the core data document which will hold my objects in the same way, and I want some default classes that make it easier to write my tests. Instead of doing that every time, it seemed more appropriate to create a project for it (even though I still have only one project for iPhone).

It’s quite possible that in the projects I develop to make posts in this blog, I’ll use intern to make my job easier if it doesn’t get in the way to illustrate the points I want to make.

Also, there’s an Intern version for java and android applications too. It will also be made open source when I close kotoba’s version 0.1 for iphone and start to develop version 0.2 to both iPhone and Android. I don’t make it open source now because I have to work a little for it to be ready.

Anyway, you can check intern at  https://github.com/rafaeladson/intern-objc


Sunday, March 4, 2012

How to test storyboard iOS view Controllers.

I’m building an IOS 5.0 storyboard based application. As I began to write my controllers, I wanted a way to test things like:

1. You type this on this text box
2. You click on save.
3. Assert that the thing is saved on database.

I used to solve this kind of problem by instantiating testFields manually during the test and setting on the controller. However, when I tried using UITextView, I found out that I couldn’t instantiate an UITextView manually because  it should be instantiated on the main thread.

So I began to google for it, and at first it wasn’t promising. People kept telling me that to do that sort of testing I would have to use UIAutomation, but I was hoping to use UIAutomation to make tests that were more like system testing, and I really wanted to do some white box testing, like checking the database, which would be difficult or even impossible with UIAutomation.

So after a few more searches, I came across this blog: http://blog.carbonfive.com/2010/03/10/testing-view-controllers/. And it is good, because now I’ve seen how I can get a viewController with all the views already instantiated, which both simplified my tests and gave me hope that I could solve my problem.

However, this is how the author of the article instantiate a view controller:

    TestableSimpleViewController *viewController = [[TestableSimpleViewController alloc] initWithNibName:@"TestableSimpleViewController" bundle:nil];

This was bad for me, because in storyboards applications, the .nib file is encapsulated inside a .storyboard file, and I didn’t want to break this encapsulation nor did I want to recreate the nib files. So it was time to hit the apple documentation (which, fortunately, is very good). In this post I will describe the solution I came up with.

First, the project
I’ve came up with a one screen view project as a demonstration. It is a sample application where the user has two text boxes, and he types something on the first one, hit the button that says Copy Text, and the text is reproduced on the second text field.

This is the screenshot of my storyboard.



The first thing I did was create another target, called UnitTests, where I would put my tests. I then setup this second target to use GHUnit as explained in the GHUnit documentation located at http://gabriel.github.com/gh-unit/docs/appledoc_include/guide_install_ios_4.html. (The gunit documentation is a little outdated, but it is not difficult to adapt it to ios 5. You can use OCUnit also, but this tutorial will be demonstrated using GUnit).

Also make sure that both the iPhone.storyboard and all classes involved on the testing are also included in the UnitTests target. The way you can do this is clicking on each class, selecting the File Inspector, and making sure in the Target Membership view that UnitTests is selected for each file.

The last thing I gotta show about the project is the code. This are the contents of ViewController.h (my only ViewController class):


@interface ViewController : UIViewController
@property (weak, nonatomic) IBOutlet UITextField *originalTextField;
@property (weak, nonatomic) IBOutlet UITextField *resultTextField;
- (IBAction)copyText:(id)sender;

@end

I have to outlets, one for each textField, and also an IBAction. The important code is the copyText function, which is replaced below:

- (IBAction)copyText:(id)sender {
    self.resultTextField.text = self.originalTextField.text;
}

So now that I have everything in place, let’s test this.

Testing Setup
So, for this demonstration I will make one simple test that checks that if I click on the save text button, the second text box will be filled with the contents of the first box.

The first thing I gotta do is get the controller reference. To do this, I use the UIStoryboard object.

@interface ViewControllerTest : GHTestCase

@property (strong, nonatomic) ViewController *controller;

@end


@implementation ViewControllerTest

@synthesize controller = _controller;


-(void) setUp {
    UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"MainStoryboard_iPhone" bundle:nil];
    self.controller = [storyboard instantiateViewControllerWithIdentifier:@"Bob"];
    [self.controller performSelectorOnMainThread:@selector(loadView) withObject:nil waitUntilDone:YES];
   
}

-(void) testCopyText {
   
   
}

@end


In the setUp method I instantiated a storyboard with a name that is equal to the name of the storyboard bundle without the .storyboard extension.

Second, I instantiated a controller from the storyboard. In order to do this, I had to give the name of the controller (in this case, Bob).

So how do the storyboard now which of my controllers has the name Bob. Well, I had to tell it, of course. The way I do this is:
1. edit the storyboard file
2. select the view controller that I want to name
click on the attributes Inspector
3. On the View Controller section, that is a TextField labeled Identifier. Type Bob there.




The last thing I had to do is call a load view in the main thread. And now my controller is ready to be used.


Writing the test

Now, what’s missing is implementing the testCopyText function.

-(void) testCopyText {
    [self.controller.originalTextField performSelectorOnMainThread:@selector(setText:) withObject:@"foo" waitUntilDone:YES];
    [self.controller copyText:nil];
    GHAssertEqualStrings(@"foo", self.controller.resultTextField.text, nil);
   
}

The first I thing I did was set the text on the originalTextField. However, as this text can only be set on the main thread (since I’m interfering with the view), I used the prformSelectorOnMainThread method to do it.

The rest is pretty straightforward. I call the copyText: method and verify that the second resultTextField was set correctly.

Source code
You can find the source code at github.