Friday, May 20, 2011

UI Automation Gotchas (part I)

I have recently started to implement user automation in Rex Lists, as a prove of concept test for the applications I am currently working on. I won't try to explain UI automation in this article, since there are are some good sources available like Alex Vollmer article or session 306 of WWDC10. Instead, I will focus on some of the things that I stumbled upon.

What is UI Automation

User Interface Automation provides developers with the ability to create test cases for the user interface that can be re-executed with almost no human intervention in order to have repeatable behavior.

As Graham Lee (@iamleeg) pointed out in the recent episode 18 of iDeveloper Live about TDD, this will help your Q&A, but cannot be considered as part of TDD, because they take a long time to be performed and thus cannot be part of the normal building process.

The things you want to know


Solve your accessibility first


Since UI Automation accesses the items using their accessibility labels, it is a good idea to have your application accessible first. You probably want to go there anyhow for the best customer experience, and it will make your live much easier too.

Use the proper language

I am not referring here to the programming language (or swearing for what is worth), but to the language that is displayed when running the program, either in the simulator or in the device. UI Automation uses the accessibility information, and if you have done your work well, that information is internationalized and displayed or spoken in the current localization.

If you want to simulate a tap on the information button that is at the left of the navigation bar, you will include some javascript code similar to:
var window = UIATarget.localTarget().frontMostApp().mainWindow();
window.navigationBar().buttons()["Information"].tap()

Unfortunately, if you run the application in Spanish (for example), the accessibility label of that item will be "InformaciĆ³n" instead of "Information" and the script will not work.

The easiest way to go is to have all the names of your UI automation script written on the default language, and ensure that all the tests are run with that language set. This means that you have to check your internationalization preferences before starting the tests.

Find the right elements

If you are not used to write UI Automation scripts, and don't know the name of the elements in any of the views of your application you can ask JavaScript to show the name of the elements.
var window = UIATarget.localTarget().frontMostApp().mainWindow();
window.logElementTree();

When unsure about the hierarchical relation of the elements, use this to get this information displayed in the output of the test as shown in the following image:
The numbers that are shown at the beginning of each line are the level in the hierarchy. In this case, the application contains a window, that contains a tableview with a table group that has its section title.

Obtain the labels for the system items

Some items, like the add button that is present in many navigation bars and toolbars, are created using the defaults provided by the system.

If you want to see the name (i.e., accessibility label) of an item that is defined by UIKit, or one of yours for what is worth, you can turn on the accessibility inspector of the simulator. In order to do so, go to the Preferences application of the simulator, tap on "General" and then on "Accessibility".

But don't forget to toggle the accessibility inspector of before running your script. Otherwise, the application will behave as if accessibility were turned on in the phone, and controls are then selected with a tap and used with a double tap.

Conclusions

I hope this information is useful to you when implementing your UI Automation scripts. I will be glad to have any comments or corrections. Share your experiences here too!

In my next entry I will explain other aspects of UI automation that are more related to its integration with Xcode. Come back and read it!

Wednesday, December 1, 2010

Sharing a Project with a Customer

Recently, I have finished a project for a customer. The project enables the customer to publish a set of images as an application and can be reused for other sets (creating a different application).

Although the customer is not a programmer both him and me wanted him to keep the project and submit his application directly to the App Store. That meant that I had to share a project with the customer that he could change (the image sets at least) and he would create the app and sign it to submit it to the App Store.

Requirements


Having worked in security for some years I try to follow the need-to-know principle and always prefer to decide what to share pro-actively than sharing all and remove the unwanted things. I do trust my customers, but in this case it made no sense to share the code, because it would be more overwhelming for a non technical person and any accidental change would result in unnecessary support time.

Also, I wanted to keep track of the project history in git, but that is another thing that makes no sense to share.

Finally, I wanted to allow my customer to compile and run the app both for the simulator and the devices.

Looking for solutions


The most obvious way to share the project without the source code is using a static library. I had never done one before in Xcode, but I could easily find some good resources on the web. By far Mostly Torn's was the best explanation, but it assumed that it would share a modified version of the project with the customer and that had some implications:
  • I had to make a full copy of the project and do some cleaning afterwards, like some text files that are for my reference or the git history. This could led to undesired results if I forgot to remove any file that wasn't meant to be shared.
  • If my customer cleaned all targets, deliberately or otherwise, it would end up with an unusable project, because the libraries would be deleted and couldn't be regenerated again.

I also attempted to add the library to the project, but couldn't figure out a way to include both the arm and the i386 versions, and wasn't able to create a fat static library from within Xcode (my fault for sure) although some solutions were available from the command line.

Final implementation


So, if you want to solve this problem too and this is what you got so far, you can try this steps and see if they fulfill your needs:
  1. Once your project is ready to be shared, create a new target for all the sources that you want to share in the static library and copy them to the compile sources build phase of that target.
  2. I also needed to include my precompiled prefix header file to the target configuration (prefix header setting), because of some header dependencies that were assumed.
  3. Build the static library for both the simulator and the device. This libraries will be created under build/Debug-iphonesimulator and
    build/Release-iphoneos
  4. Create a new project with Xcode. If this is an iOS application choose the View Application template (or the one that matches your project better.) This is the project that you will be sharing.
  5. Copy the application delegate sources to the new project directory (in the Classes subdirectory.)
  6. Copy the resources (xibs, icons, launch images, plists, etc.) to the new project directory and add them to the project by right-clicking on the resources Group and selecting Add -> Existing files...
  7. Copy the headers that are required for the main ViewController to the new project and add them to it by right-clicking on the Classes Group and selecting Add -> Existing files...
  8. Create a directory on the new project that is called lib and two inside of it that are called iphoneos and iphonesimulator.
  9. Copy the arm and the i386 versions of the library to the corresponding directories.
  10. Modify the information for the project so it contains the following settings:
    • Other linker flags -ObjC -lyourLib
    • Library Search Path lib/$(PLATFORM_NAME)
  11. Compile and test the new project, first on the simulator and then on the device.



Keep in mind, that your customer will have to edit the Bundle Identifier of the Info.plist of your shared project so it matches the distribution profile.

Want to share your thoughts?

Tuesday, November 9, 2010

SCRUM for one

Intro



As you may already know Scrum is a methodology for iterative and incremental software development that can improve both your product development cycle and its results. I first heard about it about one year ago, in Jessica Kahn's talk for the spring 2009 edition of the well deservedly famous iPhone Application Programming course that Stanford publishes in iTunesU, and since then I have applied it to all my software projects with very satisfying results. However, I had to do several adaptations because Scrum is really meant for a group of people and I develop software by myself.

Please be my guest and get to know how I apply Scrum for one.

What you need to know about Scrum



The main idea behind Scrum, the one that pushed me to adopt it, is that, instead of developing all the features of your product at once, so it becomes the Perfect Thing™ that you have in mind, you go through development sprints and at the end of each one you have ready-to-use product, that you could (more or less) ship.

The sprints can range from one week long, because less is meaningless for iteration, to a month, because more pushes you into too complicated features that render your product into an un-compilable state forever.

The features that you implement in each sprint come from a list, called backlog, that is the result of the design phase plus the constant input from the stake holders, as represented by the Product Owner and agreed with the Scrum Master (i.e. project manager).

Each spring is preceded by a meeting that is used for deciding the goals for the next sprint and followed by another one to review the accomplishments and the lessons learned. Also, in order to gather everybody's status the team holds the daily scrum meeting that shouldn't last more than 15 min.

The adaptation for one



As an indy you might be tempted to avoid implementing some of the features of the methodology. Rightly so. The most obvious one would be the meetings, but I don't recommend you to do so. Don't get me wrong. I am not suggesting that you present your slides with the desired functions of the product to yourself. However, if you are a GTD practitioner, the sprint review fits nicely in the weekly review, especially if you do sprint cycles that last a whole number of weeks. Also it is very helpful to dedicate 5 minutes a day, when you start your product development tasks, to refresh your current status. And finally you have the Scrum planning meeting.

After having done your initial design, that you have reflected into a mind-map. You dump all this features into a backlog. While as a GTD defender I reject to prioritize my actions with the ABC system because of its dynamic nature, I believe that it makes a lot of sense for features. You can classify your features in Must have, Desired features and Nice to have. So after the sprint review that happens during the weekly review, you should dedicate some time to plan what you will do in your next sprint cycle. That is the sprint kick off.

By now, you are probably thinking that this is a lot of management and not too much programming progress. So let me also talk a little bit about how I work on the features (and bug fixing) that are included in the sprint. I usually do one week sprints and manage all these changes using Git. I use a branch that is called NextVersion that holds all the changes that I plan to include in the next version of the product. From this branch I create a branch for each sprint. And from the sprint branch, I create a branch for each of the features. Once a feature is implemented and tested, I merge it to the sprint branch. And if the sprint is considered successful and I like what I have implemented, I merge the sprint branch into the NextVersion.

Conclusion



You might think "So what? I already try to do that." But as put by Diego Piacentini (Amazon) in the November 3rd talk for Stanford's ETL these are good intentions and what you really need is a process. Hope this process matches your needs. It works for me, indeed.


As always your comments are more than welcome. Let me know if you like this kind of content and share your experiences if you like.

Monday, November 8, 2010

Automatic version number from Git

Intro



I wrote this piece a while ago in my notebook, that I keep in TiddlyWiki, but listening to Jose's great series on Git in 85%Cocoa (in Spanish), I decided to share it with you in this blogger site that I had created some time ago and never really used. If you like it, I will try to publish more.

I use git for the version control of my projects and although it isn't news that Xcode 4 will come with Git integration, while Xcode 3.2 is still the required version for submitting apps to the App Store, I do want to enjoy the benefits of automatic version number generation in my projects. If you do to, hope this helps. Daniel Jailcut and Markus Zarra had previous versions that helped me to write this (Thanks guys!). Read those too.

iPhone and Mac OS X version numbers are stored in the plist of the project. There are two variables in that file that control the version number of an application:
  • CFBundleShortVersionString specifies the release version number of the bundle, which identifies a released iteration of the application. The release version number is a string comprised of three period-separated integers. The first integer represents major revisions to the application, such as revisions that implement new features or major changes. The second integer denotes revisions that implement less prominent features. The third integer represents maintenance releases. This is the marketing version.

  • CFBundleVersion identifies an iteration (released or unreleased) of the application. This is the build number.


I would like to create a script to be integrated as an XCode target that does automatic updating of this two fields of the info.plist file.

Git commit numbers are non sequential. Actually, they are hashes of the committed content. It is necessary to have a sequential number for the build number if you want to use this method both for iPhone and Mac OS X projects. In particular, both the App Store and the Sparkle framework (and probably the Mac OS X, although I haven't tried yet) require this sequential number in order to work properly.

Solution



The sequential number that is required can be maintained in a file that is not controlled by Git, so that branching or reverting changes does not change back the number. The build phase script will update the value automatically every time a build is successful. If this happens every time the project is built, successfully or not, the number would grow very fast although the numbers between successful builds are not needed.

When branches are used, which is one of the strengths of Git, a higher build number can be assigned to a patch of a current version than to the yet to release newest version. In order to add some sequence even in those cases, I will precede the sequential build number with the marketing version (1.0.0, 1.1.2, or 2.0.0 for example). In this way it is clear that 1.1.0.139 is not newer than 2.0.0.127, although the build number is.

The marketing version will only be modified if any commit has been tagged, containing the marketing version number.

In order to obtain a clean git status, build_number should be added to the .gitignore file.

Additionally the file with the build number could be copied to another file number that is tracked with Git, so that reverting to a previous commit contains information about the build number that was used at the time of the commit.

Implementation



I have created a perl script that does this using the PerlObjCBridge to read and write the plist, reads the build number from the file, and pipes to communicate with git to use describe --tags (that provides the last tag assigned).

The script is:
#!/usr/bin/perl
# gitversion.pl
#
# Created by Jorge D. Ortiz Fuentes on 09/11/09.
# Copyright 2009 PoWWaU. All rights reserved.

use Foundation;
use File::Copy;

# Script customizable variables
$git="/usr/local/git/bin/git";

$script_action = $ENV{'ACTION'};
$info_plist = $ENV{'INFOPLIST_FILE'};
#$project_dir = $ENV{'PROJECT_DIR'};
#$project_name = $ENV{'PROJECT_NAME'};
#$src_root = $ENV{'SRC_ROOT'};

if (script_action == "build") {

print "Working with project properties: $info_plist\n";
unless (-e "$info_plist.old") {
copy($info_plist, "$info_plist.old") or die "Copy failed: $!";
}

# Read the info.plist file
$project_plist = NSMutableDictionary->dictionaryWithContentsOfFile_($info_plist);

if (!$project_plist or !$$project_plist) {
die "One of the following failed: $plist - $$plist\n";
} else {
# Obtain the last tag to set the marketing version (CF
if (open(GIT_TAG, "$git describe --tags HEAD |")) {
$marketing_version = ;
chomp($marketing_version);
close(GIT_TAG);
print "Marketing version: |$marketing_version|\n";
$project_plist->setObject_forKey_(NSString->stringWithCString_($marketing_version), 'CFBundleShortVersionString');
} else {
print "Failed to obtain git describe: $!.\n";
print "Not updating CFBundleShortVersionString\n";
}

# Obtain the CFBundleVersion (or build number)
# For non sequential numbers use: git rev-parse --short HEAD.
open(GIT_BUILD, "build_number") || die "Failed to obtain git rev-parse: $!.";
$build_number = <git_build>;
chomp($build_number);
close(GIT_BUILD);
$bundle_version="$marketing_version.$build_number";
print "Build version: |$bundle_version|\n";
$project_plist->setObject_forKey_(NSString->stringWithCString_($bundle_version), 'CFBundleVersion');

# Write the updated plist.
$project_plist->writeToFile_atomically_($info_plist, "1");
}
} else {
print "Doing NOTHING. ACTION isn't 'build'";
exit(1);
}


Integration



Setting the information into the info.plist



  1. Create a new build phase for the current target that consists in running an script.

  2. Add the following script to that build phase:


  3. run_script="gitversion.pl"

    echo "Running a custom build phase script: $run_script"
    "$PROJECT_DIR/Scripts/$run_script"
    script_exit_status=$?
    echo "Finished running custom build phase script: $run_script (exit status = $script_exit_status)"
    exit "$script_exit_status"

  4. Copy the perl script into a new project directory named Scripts with the name gitversion.pl.

  5. Add the script file to the project ensuring that it is NOT added to any of the targets of the project.

  6. Move the build phase to the beginning in order to ensure that the info.plist is modified before copying it to the bundle.


Incrementing the build number



  1. The first time the project is created, initialize a file with the build number set to 1 in the main folder of the project.

    echo "1" > build_number

  2. Create a new build phase that consists on running a script.

  3. Add the following script to that build phase:

    echo "Incrementing sequential build number."
    # copy the previous build number for book keeping in Git
    cp build_number build_number.prev
    # and increment it
    bn=$(cat build_number)
    (( bn=$bn+1 ))
    echo $bn > build_number
    echo "Build number updated to $bn. Finished."

  4. This phase must be the last one for the target.



Additionally if I want to avoid incrementing the build number for non-successful builds, there is a setting in the build section of the Xcode preferences to "Continue building after errors" that can be disabled.