About the author
Adrian Kosmaczewski is a software developer, trainer and book author. He has shipped mobile, web and desktop apps for iOS, Android, Mac OS X, Windows and Linux since 1996.
Adrian is the author of "Mobile JavaScript Application Development" and "Sencha Touch 2: Up and Running", both published by O’Reilly.
When not writing or teaching, Adrian likes to spend time with his wife Claudia and his cat Max. He updates his blogs and his Twitter or Instagram accounts ("@akosma") as often as possible, and is happy to have new followers every day.
Adrian has studied physics in Switzerland, economics in Buenos Aires, and holds a Master in Information Technology with a specialization in Software Engineering from the University of Liverpool.
Preface
Mobile applications have taken the world by storm. Thousands of new applications are published every day, and they are getting every day more complex as customer demand more and more from their apps.
The need for high quality standards in mobile apps will only increase in the future; this booklet is a humble compilation of tips, tricks and techniques that can yield better code and happier teams.
I hope that these lines will help you to increase your productivity and the sales of your apps!
Oron-la-Ville, January 2013.
Introduction
Mostly driven by iOS and Android, mobile developers are faced with increasingly complex requirements and deployment issues, as the market for mobile applications grows constantly.
This book is a practical guide for testing mobile applications for iOS or Android devices. It includes an overview of the most important tools available, either provided from the platform vendors, or available in the market as open source or commercially.
Audience of this Book
This book is aimed to mobile developers, proficient in either iOS and/or Android, willing to increase the quality of their applications. They should be comfortable with the general concepts of software quality management, which will not be covered in this book.
Structure of the Book
This book is divided in two sections, covering the same subjects for both iOS and Android applications:
-
Defensive coding techniques;
-
Unit testing;
-
Functional testing.
For each platform, the book covers the most important frameworks and tools available in each area.
Sample Application
We are going to use a very simple application to showcase different technigues used to test iOS and Android apps: an integer calculator, implemented in the most naive and simple way.
This application uses an MVC architecture, using a class named
AKOIntegerCalculator
for iOS and IntegerCalculator
for Android,
encapsulating the calculation logic, and an AKOViewController
class
(respectively, MainActivity
for Android) to drive the user interface.
[image_ios_sample_app] and [image_android_sample_app] show the UI of the
application running in both the iOS Simulator and the Android Emulator, which
offers basic operations and will be tested throughout the following chapters.
Technical Requirements
To use the code examples and the instructions of this book, you should use a computer with the following specifications:
-
Mac with OS X Mountain Lion and the latest Xcode (available for free in the Mac App Store), and the latest Android SDK already installed, including Eclipse.
-
Windows or Linux laptop with the latest Android SDK and Eclipse.
-
iOS and/or Android devices (provisioned and ready to be used in development.)
In all cases, the computers used during this training should include:
|
At the time of this writing, the latest version of Xcode is 4.6, and the latest version of the Android developer tools is version 21.0.1, which is composed by the following components:
|
Readers of this book must be comfortable using a terminal or a command line.
Acknowledgements
The author would like to thank Duncan Scholtz, Java developer and Kishyr Ramdial, iOS developer at immedia (Durban, South Africa) for his help and precious insight during the development of this booklet.
Testing iOS Applications
Defensive Coding Techniques for iOS
This chapter will provide a short introduction to techniques used to increase the quality of your iOS applications.
NSError, NSException and NSAssert
Cocoa offers different mechanisms to notify about unexpected critical events. They are meant to be used in different contexts, ranging from coding time to runtime.
NSAssert
An assertion is code that’s used during development that allows a program to check itself as it runs. When an assertion is true, that means that everything is operating as expected. When it’s false, that means that it has detected an unexpected error in the code. They are specially useful in large, complicated programs and in high-reliability systems.
When to use Assertions
Assertions can be used to check the following assumptions:
-
That the values of function or method parameters fall within expected ranges;
-
That files are open (or closed) when a routine starts (or ends) executing;
-
That files are writeable (or not);
-
That the value of an input is not changed by a function;
-
That a pointer is not null;
-
That an array contains at least a certain number of elements;
-
That an array has been initialized to some state before execution;
-
That a container is empty;
-
That a computation result match a particular set of assumptions before being returned to the caller.
Guidelines for Assertions
Remember to follow the following guidelines when using assertions:
-
Use error-handling for conditions that are expected to occur; use assertions for conditions that should never occur;
-
Avoid putting executable code into assertions;
-
Use assertions to check for pre- and post-conditions in methods and functions.
-
For higher robustness, assert and then handle the error anyway.
|
You can think of assertions as executable documentation, acting as meaningful comments helping developers understand the assumptions made by the creator of a particular set of code lines. |
Assertions in Cocoa
Cocoa offers the following assertion macros to be used exclusively in Objective-C code:
NSAssert(condition, desc, …)
NSAssert1(condition, desc, arg)
NSAssert2(condition, desc, arg, arg)
NSAssert3(condition, desc, arg, arg, arg)
NSAssert4(condition, desc, arg, arg, arg, arg)
NSAssert5(condition, desc, arg, arg, arg, arg, arg)
NSParameterAssert(condition)
The same assertions are available for use inside of C functions:
NSCAssert(condition, NSString *description, …)
NSCAssert1(condition, NSString *description, arg)
NSCAssert2(condition, NSString *description, arg, arg)
NSCAssert3(condition, NSString *description, arg, arg, arg)
NSCAssert4(condition, NSString *description, arg, arg, arg, arg)
NSCAssert5(condition, NSString *description, arg, arg, arg, arg, arg)
NSCParameterAssert(condition)
|
The |
An NSAssert
will throw an exception when it fails. So NSAssert
is there to
be short and easy way to write and to check any assumptions you have made in
your code. It is not an alternative to exceptions, just a shortcut. If an
assertion fails then something has gone terribly wrong in your code and the
program should not continue.
One thing to note is that NSAssert
will not be compiled into your code in a
release build.
NSException
Exceptions, as the name implies, are used to signal exceptional events that threaten the correct execution of an application, and are most probably due to programming errors or runtime conditions out of reach from the developer.
The times you would @throw
your own NSException
are when you definitely want
it in a release build, and in things like public libraries/interface when some
arguments are invalid or you have been called incorrectly. Note that it isn’t
really standard practice to @catch
an exception and continue running your
application. If you try this with some of Apple’s standard libraries (for
example Core Data) bad things can happen. Similar to an assert, if an exception
is thrown the app should generally terminate fairly quickly because it means
there is a programming error somewhere.
Guidelines for Using Exceptions
Follow these guidelines when using exceptions in your applications:
-
Use exceptions to notify about errors that cannot be ignored.
-
Throw only when exceptional situations occur.
-
Do not throw if you can solve the error locally.
-
Avoid throwing in
init
ordealloc
(to avoid memory leaks and inconsistencies) unless you@catch
inside of them. -
Think of exceptions as part of the class interface; clients should be aware of them, in a similar way to Java’s own checked exceptions.
-
Include in the exception message all the information that led to the exception.
-
Avoid empty
@catch
blocks. -
Consider having a centralized exception reporter.
-
Standardize the use of exceptions in your projects.
-
Consider alternatives (logging, NSError, etc).
Setting a Global Uncaught Exception Handler
Cocoa allows iOS developers to set a global uncaught exception handler to their applications:
Although the application dies shortly after executing the global uncaught
exception handler, a common technique is to set a applicationDidCrash = YES
value in NSUserDefaults
and to check that value when the application starts
again.
NSError
NSErrors
should be used in your libraries/interfaces for errors that are not
programming errors, and that can be recovered from. You can provide
information/error codes to the caller and they can handle the error cleanly,
alert the user if appropriate, and continue execution. This would typically be
for things like a "File not found" error or some other non-fatal error.
|
To put it more strongly, an NSException should not be used to indicate a recoverable error. In other words: Obj-C’s NSException == Java’s Error class, and Obj-C’s NSError == Java’s Exception class. |
Error Handling Techniques
To handle errors, a certain routine might choose to do any (or all) of the following:
-
Return neutral values, such as
0
,@""
(empty string) or aNULL
pointer. -
Skip to the next piece of valid data, such as when dealing with long lists of records sequentially.
-
Return the same answer as the last time the method was called.
-
Substitute the return value by the closest leval value.
-
Log the event in a log file.
-
Return an error code (very common in Cocoa, using an
NSError **
parameter). -
Call an error processing routine.
-
Display an error message whenever the error appears (beware of security and usability implications!)
-
Shut down (for critical systems, it might be the only possible choice!)
Code Defensively
This section showcases some simple techniques that you might want to adapt to your own team workflow. The objective being that the code produced by your organization is predictable and consistent.
Treat warnings as errors
First of all, why does the Objective-C compiler (or compilers in general) output “warnings”? Many developers are puzzled the first time they encounter them, since even if the compiler complained, the application usually runs anyway without (perceptible) problems.
Warnings are used to signal specific issues in the source code which could potentially lead to crashes or misbehavior under some circumstances, but which should not (pay attention to the verb “should”) block the normal compilation and (hopefully) execution of your code (otherwise, it would be a compiler error).
It’s the way used by your compiler to say:
Hey, I’m not sure, but there’s something fishy in here.
Not removing warnings, as I said above, is a problem that originates both in the programming background of the developer, and specific technical issues.
Culturally speaking, many other programming environments either do not have compilers at all (at least not “visible” ones, like Ruby or PHP) or simply do not spit warnings for anything else than deprecated methods (like C# or Java); this situation has made many developers new to the iPhone platform to blatantly ignore them.
Technically, given the fact that Objective-C is the “other” object-oriented superset of C, and that it behaves as a coin with both a static and a dynamic side, compiler warnings convey a great amount of precious information that must never be ignored.
In this sense, Objective-C has a lot in common with C. Ignoring warnings in
C
is strongly discouraged, and Scott Meyers explains this in chapter 9 of his
book “Effective C++”, stating that (third edition, page 263):
Take compiler warnings seriously, and strive to compile warning-free at the maximum warning level supported by your compilers.
In the case of Objective-C, this can be done by setting a particular value named GCC_TREAT_WARNINGS_AS_ERRORS (-Werror) to true in your build settings, as shown in the figure below.
Steve McConnell takes this advice to another level of importance in his classic book “Code Complete” (second edition, page 557):
Set your compiler’s warning level to the highest, pickiest level possible, and fix the errors it reports. It’s sloppy to ignore compiler errors. It’s even sloppier to turn off the warnings so that you can’t even see them. Children sometimes think that if they close their eyes and can’t see you, they’ve made you go away (…).
Assume that the people who wrote the compiler know a great deal more about your language than you do. If they’re warning you about something, it usually means you have an opportunity to learn something new about your language.
To give a concrete example of the importance of warnings, many of us have had to migrate applications developed for iPhone OS 2.x to the 3.0 operating system, mostly because failure to run on the new version of the OS was ground for removal from the App Store. That moment of truth, the rebuild of the Xcode project, unveiled a plethora of compiler warnings, most due to deprecated methods, like the tableView:accessoryTypeForRowWithIndexPath: method of the UITableViewDelegate protocol, or the initWithFrame:reuseIdentifier: method of the UITableViewCell class (which, incidentally, are properly marked as such in the documentation, too).
Compiler warnings in Objective-C have a multitude of reasons:
-
Using deprecated symbols;
-
Calling method names not declared in included headers;
-
Calling methods belonging to implicit protocols;
-
Using some ambiguous commands which might be intentional but are syntactically valid anyway;
-
Forgetting to return a result in methods not returning “void”;
-
Forgetting to #import the header file of a class declared as a forward “@class”;
-
Downcasting values and pointers implicitly.
Organize your code
Each @implementation *.m file should always present methods in this order:
-
init and dealloc
-
public methods
-
public @dynamic properties
-
delegate methods (for each supported protocol)
-
private methods
Use #pragma mark statements
Each logic group of methods should be separated from each other using the following lines (just type “#p” and hit the TAB key in Xcode to speed up the process):
The advantage of this approach is that later, you can use those #pragma marks to generate an automatic layout in the symbols pop-up of Xcode, as shown in the figure below.
You can get this pop-up window clicking on the “Jump Bar” of the current Xcode editor.
Only advertise public methods in header files
This means putting private methods definitions in a (Private) category on top of the *.m file. This will remove all compiler warnings (about “this class might not respond to this selector”) and will cleanly separate what’s public from what’s not.
Use the Scientific Method of Debugging
-
Stabilize the error
-
Locate the source of the error:
-
Gather the data that produces the defect.
-
Analyze the data that has been gathered, and form a hypothesis about the defect.
-
Determine how to prove or disprove the hypothesis, either by testing the program or by examining the code.
-
Prove or disprove the hypothesis by using the procedure identified in 2(c).
-
-
Fix the defect.
-
Test the fix.
-
Look for similar errors.
Use consistent coding conventions
At the end of this document, [chapter_ios_coding_guidelines] contains generic and important coding conventions. You can start from this document to create your own.
Debugging Techniques
This section will highlight interesting debugging techniques available in iOS.
Add Context Information to Log Messages
The C preprocessor provides a number of standard macros that give you information about the current file, line number, or function. Additionally, Objective-C has the _cmd implicit argument which gives the selector of the current method, and functions for converting selectors and classes to strings. You can use these in your NSLog statements to provide useful context during debugging or error handling.
Macro | Format Specifier | Description |
---|---|---|
|
%s |
Current function signature. |
|
%d |
Current line number in the source code file |
|
%s |
Full path to the source code file. |
|
%s |
Like func, but includes verbose type information in C++ code. |
Expression | Format Specifier | Description |
---|---|---|
|
%@ |
Name of the current selector. |
|
%@ |
Name of the current object’s class. |
|
%@ |
Name of the source code file. |
|
%@ |
NSArray of the current stack trace as programmer-readable strings. For debugging only, do not present it to end users or use to do any logic in your program. |
Inspecting Objects
All Cocoa objects (everything derived from NSObject
) support a description
method that returns an NSString
describing the object. The most convenient way
to access this description is via Xcode’s Print Description to Console menu
command. Alternatively, you can use LLDB’s print-object
(or po
for short)
command.
Adding Exception Breakpoints
Xcode 4 offers a simple way to generate a breakpoint whenever an exception occurs. A great idea when you need to track down application crashes.
In the break point navigator click the plus (+) symbol in the lower left corner. Choose "Add Exception Breakpoint". The exception breakpoint options are shown in [image_ios_exceptions_breakpoint].
Beware though that all exceptions will be logged by this breakpoint, not only
uncaught exceptions, but also those surrounded by @try
/ @catch
statements.
You might want to check the "Automatically Continue after Evaluating" checkbox
to avoid stopping too often.
Inspecting Memory Management
You can use -retainCount
to get the current retain count of an object. While
this can sometimes be a useful debugging aid, be very careful when you interpret
the results. For example:
(lldb) set $s=(void *)[NSClassFromString(@"NSString") string]
(lldb) p (int)[$s retainCount]
$4 = 2147483647
(lldb) p/x 2147483647
$5 = 0x7fffffff
The system maintains a set of singleton strings for commonly used values, like the empty string. The retain count for these strings is a special value indicating that the object can’t be released.
Another common source of confusion is the autorelease mechanism. If an object
has been autoreleased, its retain count is higher than you might otherwise
think, a fact that’s compensated for by the autorelease pool releasing it at
some point in the future. You can determine what objects are in what autorelease
pools by calling _CFAutoreleasePoolPrintPools
to print the contents of all the
autorelease pools on the autorelease pool stack:
(lldb) call (void)_CFAutoreleasePoolPrintPools()
- -- ---- -------- Autorelease Pools -------- ---- -- -
==== top of stack ================
0x327890 (NSCFDictionary)
0x32cf30 (NSCFNumber)
[…]
==== top of pool, 10 objects ===============
0x306160 (__NSArray0)
0x127020 (NSEvent)
0x127f60 (NSEvent)
==== top of pool, 3 objects ================
- -- ---- -------- ----------------- -------- ---- -- -
Zombies
A common type of bug when programming with Cocoa is over-releasing an object.
This typically causes your application to crash, but the crash occurs after the
last reference count is released (when you try to message the freed object),
which is usually quite removed from the original bug. NSZombieEnabled
is your
best bet for debugging this sort of problems; it will uncover any attempt to
interact with a freed object.
The easiest way to enable zombies is via Instruments, as shown in [image_ios_zombies_instruments]. However, you can also enable zombies in Xcode, as shown in [image_ios_zombies_xcode]. To do that, hit the "Option-Command-R" key combination (or select "Product / Run" while holding the "Option" key) and select the "Diagnostics" tab; check the "Enable Zombie Objects" option in the dialog.
Key-Value Observing
If you’re using Key-Value Observing and you want to know who is observing what
on a particular object, you can get the observation information for that object
and print it using using LLDB’s print-object
command.
(lldb) # self is some Objective-C object.
(lldb) po self
<ZoneInfoManager: 0x48340d0>
(lldb) # Let's see who's observing what key paths.
(lldb) po [self observationInfo]
<NSKeyValueObservationInfo 0x48702d0> (
<NSKeyValueObservance 0x4825490: Observer: 0x48436)
Finding Non-Localized Strings
You can set the NSShowNonLocalizedStrings
preference to find strings that
should have been localized but weren’t. Once enabled, if you request a localized
string and the string is not found in a strings file, the system will return the
string capitalized and log a message to the console. This is a great way to
uncover problems with out-of-date localizations.
Debugging UIViews
UIView
implements a useful description
method. In addition, it implements a
recursiveDescription
method that you can call to get a summary of an entire view
hierarchy.
(lldb) po [self view]
<UIView: 0x6a107c0; frame = (0 20; 320 460); autoresize = W+H; layer = […]
Current language: auto; currently objective-c
(lldb) po [[self view] recursiveDescription]
<UIView: 0x6a107c0; frame = (0 20; 320 460); autoresize = W+H; layer = […]
| <UIRoundedRectButton: 0x6a103e0; frame = (124 196; 72 37); opaque = NO; […]
| | <UIButtonLabel: 0x6a117b0; frame = (19 8; 34 21); text = 'Test'; […]
Debugging Core Data Objects
If you’re working on an app that uses Core Data, it’s inevitable that you’ll end
up in the debugger and need to dig around in the object graph. You’ll also
quickly realize that Core Data’s -description
of an object isn’t terribly
helpful:
(lldb) po myListObj
(List *) $19 = 0x08054ac0 <List: 0x8054ac0> (entity: List; id: 0x8074280 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/List/p175> ; data: <fault>)
Core Data’s documentation is excellent, but surprisingly doesn’t cover some of the tricks you can use to examine managed objects in the debugger.
The first trick is to fire a fault on the object using -willAccessValueForKey
.
After that, you can see what’s really there:
(lldb) po [myListObj willAccessValueForKey:nil]
(id) $20 = 0x08054ac0 <List: 0x8054ac0> (entity: List; id: 0x8074280 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/List/p175> ; data: {
containerId = "8E4652D3-5516-4186-B1C9-DDBE41E108CF";
createdAt = "2009-10-08 15:17:53 +0000";
itemContainers = "<relationship fault: 0x8020850 'itemContainers'>";
})
You might also be surprised when you try to access one of the properties of the object:
(lldb) po myListObj.containerId
error: property 'containerId' not found on object of type 'List *'
error: 1 errors parsing expression
Remember that these properties are defined as @dynamic
and there’s a lot of work
done by Core Data at runtime to provide the implementation. The solution here is
to use the KVC accessor -valueForKey:
to get the object’s value:
(lldb) po [myListObj valueForKey:@"containerId"]
(id) $26 = 0x08069b60 8E4652D3-5516-4186-B1C9-DDBE41E108CF
Often, you’ll want to examine the relationships between objects. As you can see
in the output above, the attribute itemContainers
, which is a to-many
relationship, is a fault. To fire the fault, get all the objects from the set:
(lldb) po [[myListObj valueForKey:@"itemContainers"] allObjects]
(id) $23 = 0x0d0667b0 <__NSArrayI 0xd0667b0>(
<Container: 0x807a180> (entity: Container; id: 0x801bc50 <x-coredata://29B10357-0723-4950-9EB6-E6D7AD6269B9/Container/p1> ; data: {
containerId = TestContainer;
items = "<relationship fault: 0x805b100 'items'>";
state = "(...not nil..)";
type = 4;
})
)
Finally, you may be using Transformable attribute types. The state attribute
above is an example. If you’d like more information than (…not nil…), use
the KVC getter and you’ll see that it’s an empty NSDictionary
:
(lldb) po [[[[myListObj valueForKey:@"itemContainers"] allObjects] lastObject] valueForKey:@"state"]
(id) $29 = 0x0807dd30 {
}
(lldb) po [[[[[myListObj valueForKey:@"itemContainers"] allObjects] lastObject] valueForKey:@"state"] class]
(id) $30 = 0x01978e0c __NSCFDictionary
Useful Tools
This section will introduce some interesting tools available to iOS developers to help them code defensively.
Network Link Conditioner
To test your applications in your simulator under different network conditions, you can use the Network Link Conditioner, available as a separate download from Apple.
Get the Hardware IO Tools for Xcode. To do this, go into the Xcode menu, then choose “Open Developer Tool” and finally “More Developer Tools…”. You’ll be taken to Apple’s developer downloads site; you should download the “Hardware IO Tools for Xcode”.
The resulting disk image will contain (amongst other things) a preference pane called “Network Link Conditioner”. Double-click the prefpane file and authenticate to allow it to be installed. You’ll then see the pane in System Preferences, as shown in [image_ios_network_link_conditioner].
You can choose from various different types of network conditions using the Profile popup menu, shown in [image_ios_network_link_popup].
QuincyKit
QuincyKit is an open source system for automatic crash reporting, composed of client iOS and OS X clients and a PHP + MySQL server backend application. The client frameworks are able to report, upon restart, a complete crash report to the backend application, as shown in [image_ios_quincy_dialog].
To install a working version of QuincyKit, the following steps are required:
Server application:
-
Clone the project from GitHub.
-
Point the local web server to the
server
folder located in the source code distribution. -
Create a new database in the local MySQL server, named
quincy
. -
Execute the
database_schema.sql
SQL script to create the required structure in the database. -
Modify the parameters in the
config.php
file, corresponding to those of your server (around line 70)$server = 'localhost'; $loginsql = 'root'; $passsql = 'root'; $base = 'quincy';
-
Navigate to
http://localhost:8888/test_setup.php
to make sure that your installation is properly configured. -
Navigate to
http://localhost:8888/admin/
to access the administration interface. -
Create an application, specifying the same bundle identifier as your application (as shown in [image_ios_quincy_admin], in our example the identifier is
com.akosma.QuincyTest
). Specify a name for the application as well. -
Click on the application name to access the crash report section, shown in [image_ios_quincy_crashes].
-
Define an application version, in this case version 1.0.
Client framework:
-
Copy the following files in your project:
-
BWQuincyManager.h
-
BWQuincyManager.m
-
CrashReporter.framework
-
Quincy.bundle
-
-
Add the
SystemConfiguration.framework
to your target. -
If you are using ARC, set the
-fno-objc-arc
flag on theBWQuincyManager.m
file, in the "Build Phases / Compile Sources" section of the configuration for your target. -
In the "Build Settings" of your target, add the
-all_load
flag in the "Other Linker Flags" entry. -
Open your application delegate class, and make it conform to the
BWQuincyManagerDelegate
protocol: -
In the implementation of your application delegate, include a new line in your
didFinishLaunchingWithOptions:
method:
|
The creators of QuincyKit have created a hosted solution called HockeyApp, available for a monthly fee, which can be used without having to host its own server infrastructure. |
QuincyKit can also symbolicate crash logs, but this requires a more complex setup, taking care of the following steps:
-
In the latest versions of Xcode, the
symbolicatecrash
tool is located at the following (long!) path:/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/ Developer/Library/PrivateFrameworks/DTDeviceKit.framework/Versions/A/ Resources/symbolicatecrash
-
Make sure to add the following variable in your environment before running
symbolicatecrash
:export DEVELOPER_DIR=`xcode-select --print-path`
-
Copy the
.app
and.app.dSYM
files in a folder, so that QuincyKit can find them and symbolicate the crash logs automatically.
QuincyKit can even be configured to push notifications to developers whenever a certain number of crashes is met, via email or using Growl.
|
Check Technical Note TN2151 to understand and analyze iOS crash reports. |
NSLogger
NSLogger is a very powerful open source utility that allows developers to monitor in real time the log messages of their applications, as testers of beta users interact with the application in a local network. Applications that include the NSLogger code can interact automatically with a desktop OS X application, displaying not only text but also binary data such as images.
[image_ios_nslogger_window] shows a typical NSLogger session.
To include NSLogger in an Xcode project, follow these steps:
-
Add the following files to your project:
-
LoggerCommon.h
-
LoggerClient.h
-
LoggerClient.m
-
-
Add the required system frameworks:
-
CFNetwork.framework
-
SystemConfiguration.framework
-
-
Use the NSLogger API calls to log stuff:
-
LogMessage()
to output text; -
LogData()
to output raw binary data; -
LogImageData()
to output images; -
LogMarker()
to output a marker (arbitrary separator in the logger output.
-
The desktop OS X application is bundled as an Xcode project, that can be built and run off the box. When the application detects a new instance of the application running, it opens a new log window, and each session can be saved and viewed separately. The log output can be filtered and searched very easily.
Conclusion
Several techniques are available these days for iOS developers to increase the quality of their code; using them will help your teams increase their productivity and the quality of their products.
Unit Testing iOS Applications
Unit testing is primary mechanism to ensure that the individual components of an application work properly. It is a very important of testing, albeit not the only one. In this chapter we will see how to use two different mechanisms to test iOS applications.
OCUnit / SenTest
The OCUnit or SenTest framework, created by the Swiss company Sen:te and included in Xcode since 2004, is the primary mechanism to add unit tests to a project.
Adding Tests
It is very easy to use; whenever you create a new project, Xcode prompts the developer whether to add unit tests to it, as shown in [image_ios_sentest_dialog].